S.O.L.I.D Principle

Himanshu verma
7 min readJan 26, 2023

--

The SOLID principle was introduced by Robert C. Martin, also known as Uncle Bob and it is a coding standard in programming.

What is Solid Principle?

S stands for Single Responsibility Principle

O stands for Open/Closed Principle

L stands for Liskov Substitution Principle

I stands for Interface Segmented Principle

D stands for Dependency Inversion Principle

What are benifits of using SOLID principle?

  • Code reusability
  • Reduces coupling
  • Easy to understand
  • Easily extensible

There are other benefits too …

“Let’s understand these five principles with easy definition and easy examples” →

  1. Single Responsibility Principle :-

“A class should have one, and only one, reason to change” , it means every class should have a single responsibility or single job or single purpose.


class Person {
var name: String
var age: Int = 0
var sex: String


constructor(name: String, age: Int, sex: String) {
this.name = name
this.age = age
this.sex = sex
}
}

class Cricket {
var person: Person
var costOfKit: Int

constructor(person: Person, costOfKit: Int) {
this.person = person
this.costOfKit = costOfKit
}

/**
* As there are three reasons to get changed
*/
fun getPrice(): Int {
return if (person.sex == "M") {
costOfKit * 100
} else {
costOfKit * 150
}
}

/**
* This is the first reason here if we add GST to price then the whole class needs to be changed
*/


fun savePersonNameToDB() {
// TODO we have to save the name to DB
}

/**
* This is the second reason here we have to save person's name to data base
*/

fun updateAgeGroup() {
// TODO we have to update the age group to DB
}
/**
* This is the third reason here we have to save age group to DB
*/
}

As the example shown above has three responsibilities/reasons which would change the whole class , so it doesn’t follow this principle …

So solution for it :-

/**
*
* So we created different classes for different uses and now it follows the principle
*
*/
class CricketName {
var person: Person

constructor(person: Person) {
this.person = person
}

fun savePersonNameToDB() {
// TODO we have to save the name to DB
}
}

class AgeGroup {
var person: Person

constructor(person: Person) {
this.person = person
}

fun updateAgeGroup() {
// TODO we have to update the age group to DB
}
}

As we divided the whole class into different classes according to different work allocated to them, so now it follows Single Responsibility Principle.

2. Open/Closed Principle :-

“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification” , this statement already explains itself i.e classes/function/modules should be reused but should not be modified …..

Didn’t get it from the statement right ?

This example will help you understand it :-

/**
*
* If there is a new requirement to add something in this class then? And it was already tested class
*
*/
class Cricketer {
var person: Person

constructor(person: Person) {
this.person = person
}

fun savePersonNameToDB() {
// TODO we have to save the name to DB
}
}
/**
*
* So here we just modified already tested class, which exploits our rule .
*
*/
class Cricketer
{
var person: Person

constructor(person: Person) {
this.person = person
}
fun savePersonNameToDB() {
// TODO we have to save the name to DB
}
fun writingToFile()
{
// TODO we have to write the file
}
}

As you can see in the example we just modified a class which was already tested (added a new function to it), so it exploits our rule.

Solution for it:-

class Cricketer {
var person: Person

constructor(person: Person) {
this.person = person
}
fun savePersonNameToDB() {
// TODO we have to save the name to DB
}
}

interface CricketHelper{
fun doWorkAccordingly(cricketer: Cricketer)
}

class CricketerDB :CricketHelper{
override fun doWorkAccordingly(cricketer: Cricketer) {
//TODO save the data into DB
}

}
class CricketerFile : CricketHelper{
override fun doWorkAccordingly(cricketer: Cricketer) {
//TODO write the data into file
}

}

So we didn't make any change in existing class, instead made an interface and made two different class and extended the interface and now can easily do the required work.

3. Liskov Substitution Principle :-

“Derived or child classes must be substitutable for their base or parent classes”, it means If class Y is a subtype of class X, then we should be able to replace the object of X with Y without breaking the behaviour of the program ; Still didn’t get it?

And Object of subclass should be able to access the all the methods and properties of the superclass.

In Layman terms :- Subclass should extend the capability of parent class ,it should not narrow it down.

This means that, given that class B is a subclass of class A, we should be able to pass an object of class B to any method that expects an object of class A and the method should not give any weird output in that case.

This is the expected behavior, because when we use inheritance we assume that the child class inherits everything that the superclass has. The child class extends the behavior but never narrows it down.

Example:-


// This is let's say is Main Class
interface Custom {
fun optForOtherLanguages()
fun countOfStudents()
}

// This is subclass

class College : Custom {
override fun optForOtherLanguages() {
/**
* But this class is breaking the flow here (by throwing exception)
*/
throw Exception("We can't opt for other language in College")
}

override fun countOfStudents() {
// TODO return the total number of students
}
}

// This is subclass
class School : Custom {
override fun optForOtherLanguages() {
//TODO opt for different Languages
}

override fun countOfStudents() {
// TODO return the total number of students
}

}


/**
*
* As we can see in the class College,
* we have just narrowed it down (optForOtherLanguages function is throwing exception )
* it is just breaking the flow.
*
*/


/**
*In class School we are following the principle.
*/





// ANOTHER EXAMPLE

open class Rectangle {

var height: Int =0
private set

var width: Int = 0
private set


constructor(width: Int, height: Int) {
this.width = width
this.height = height
}

constructor()

open fun setWidth(newWidth: Int) {
width = newWidth
}

open fun setHeight(newHeight: Int) {
height = newHeight
}
}
class Square : Rectangle(){

override fun setHeight(newHeight: Int) {
super.setHeight(newHeight)
super.setWidth(newHeight)
}

override fun setWidth(newWidth: Int) {
super.setWidth(newWidth)
super.setHeight(newWidth)
}


}
/**
*
* The child class should extend the behavior but here it narrows it down,
* which is wrong.
*
* In setHeight and setWidth function we are changing their functionality,
* Not only extending the parent class function but narrowing down too.
*
*/

Hope you got it :)

4. Interface Segmented Principle :-

“Do not force any client to implement an interface which is irrelevant to them” , it basically means that Interfaces should be such that, the client should not implement unnecessary functions they don’t need.

Examples :-


interface Work {
fun shineBowl()
fun maintainScore()
fun fineBat()
}

class Batsman : Work {
override fun shineBowl() {
/**
* it is not the task of a batsman
*/
}

override fun maintainScore() {
/**
* it is not the task of a batsman
*/
}

override fun fineBat() {
//TODO he will maintain the quality of the Bat
}

}

class Bowler : Work {
override fun shineBowl() {
// TODO he will shine the bowl
}

override fun maintainScore() {
/**
* it is not the task of a bowler
*/
}

override fun fineBat() {
/**
* it is not the task of a bowler
*/
}
}

As we can see that some functions are not required for that particular classes, but we are just implementing them :(

Solution for it :-

interface BatsmanInterface {
fun maintainBat()
}

interface UmpireInterface {
fun scoreUpdate()
}

class Umpire : UmpireInterface {
override fun scoreUpdate() {
//TODO umpire will update the score
}

}

class BatsmanUpdated : BatsmanInterface {
override fun maintainBat() {
// TODO he will fine his bat
}
}

Here we just made different interfaces so that unnecessary functions are not there.

5. Dependency Inversion Principle :-

  • High-level modules/classes should not depend on low-level modules/classes. Both should depend upon abstractions.
  • Abstractions should not depend upon details. Details should depend upon abstractions.

In Layman terms :- Class should be dependent on Interfaces rather than concrete class.

Let me explain you with an example: -

class Database {
fun create() {}
fun read() {}
fun update() {}
fun delete() {}
}

class OfficeWork {
/**
*
* Here the class is being dependent on concrete class (Database)
*
*/
private val database: Database

constructor(database: Database) {
this.database = database
}

fun printingData() {
// TODO to print something from data
}

}

As in this example in class Officework , we can have only one type of database , it is dependent on a concrete class .

Solution for it :-

interface DB {
fun create()
fun read()
fun update()
fun delete()
}

class SQLDatabase :DB {
override fun create() {
TODO("Not yet implemented")
}

override fun read() {
TODO("Not yet implemented")
}

override fun update() {
TODO("Not yet implemented")
}

override fun delete() {
TODO("Not yet implemented")
}
companion object{

}
}
class MongoDB:DB{
override fun create() {
TODO("Not yet implemented")
}

override fun read() {
TODO("Not yet implemented")
}

override fun update() {
TODO("Not yet implemented")
}

override fun delete() {
TODO("Not yet implemented")
}

}

class OfficeWorkUpdated{

private val database:DB
constructor(database:DB)
{
this.database =database
}
fun printingData() {
// TODO to print something from data
}

}

Here we made an interface and extended that interface to two different class , so now class OfficeWorkUpdated can have different type of databases(as it’s type of database is of interface “DB”).

So that’s all for SOLID principles.

It is my first medium blog , please do let me know if you liked it and feel free to ask questions on my linkedin :- https://www.linkedin.com/in/himanshu-verma-318903171/

References :- https://www.geeksforgeeks.org/solid-principle-in-programming-understand-with-real-life-examples/

--

--