SOLID

Solid-exempel

image.png

1. Single Responsibility Principle:
A Class should have only one reason to change.
In other words, A class has to have only one responsibility.
If our class is doing more than one responsibility, Then we should split our class in such a manner, That the one class has only one responsibility and others have other responsibilities, Else we are violating SRP.

See the bad example of SRP:
data class Employee(
var empId: String,
var empName: String,
var empPhoneNumber: String,
var empCtc: String,
var empTakeHome: String,
var empTax: String,
var addressLine: String,
var landmark: String,
var pinCode: String,
var city: String,
var state: String
) {
fun getEmpBasicInformation() {
println("Emp Id: $empId")
println("Emp name: $empName")
println("Emp name: $empName")
println("Emp PhoneNumber: $empPhoneNumber")
}
fun getEmpSalaryInformation() {
println("Emp ctc: $empCtc")
println("Emp take home: $empTakeHome")
println("Emp tax: $empTax")
}
fun getEmpAddressInformation() {
println("Emp address: $addressLine,$landmark,$city,$pinCode,$state")
}
}

Here Employee class is managing more than one responsibility.
1 Employee info, 2 Ctc Info, 3 Address info.
So updating any of this will take more time as well as in more places, We need to maintain the code, and if we want to reuse one of the subparts so it is not reusable.

See the SRP version of the above code snippet:
data class EmployeeDetail(
val employee: Employee,
val employeeCTC: EmployeeCTC,
val employeeAddress: EmployeeAddress
) {
fun getEmployee() {
employee.getEmpBasicInformation()
}
fun getEmployeeCTC() {
employeeCTC.getEmpCTCInformation()
}
fun getEmployeeAddress() {
employeeAddress.getEmpAddressInformation()
}
}
data class Employee(
var empId: String,
var empName: String,
var empPhoneNumber: String
) {
fun getEmpBasicInformation(): String {
return "Employee(empId='$empId'," +
" empName='$empName', " +
"empPhoneNumber='$empPhoneNumber')"
}
}
data class EmployeeCTC(
var empCtc: String,
var empTakeHome: String,
var empTax: String
) {
fun getEmpCTCInformation(): String {
return "EmployeeCTC(empCtc='$empCtc'," +
" empTakeHome='$empTakeHome', " +
"empTax='$empTax')"
}
}
data class EmployeeAddress(
var addressLine: String,
var landmark: String,
var pinCode: String,
var city: String,
var state: String
) {
fun getEmpAddressInformation(): String {
return "EmployeeAddress(addressLine='$addressLine'," +
" landmark='$landmark'," +
" pinCode='$pinCode', " +
"city='$city', state='$state')"
}
}

Above we have separated each class as a responsibility, So the code is cleaner and readable, and maintainable, If we want to use any of the classes independently in many places we can do it easily, and If we want to update any class so it is much easier now.

2. Open Closed Principle:
Software entities such as classes, functions, and modules should be open for extension but closed for modification.
If we have already written the class, So it should be open for extension but closed for modification, Hence we should design the class in such a way that follows this principle(for a new feature we should not change the current class rather we should create a new class by extending the current class)

Let's see the bad example:
class MathematicalOperation {
fun doCalculation(operationName: String, firstNumber: Int, secondNumber: Int): Int {
return when (operationName) {
"Addition" -> {
//do addition operation here
firstNumber + secondNumber
}
"Subtraction" -> {
// do subtraction operation
firstNumber - secondNumber
}
else -> {
throw OperationNotSupportedException()
}
}
}
}

In the above example, We have a method that takes operation names and does the operation if we want to increase the operations so in that case, We need to touch the code of the above class and need to modify the doCalculation() method. Hence we are violating the Open close Principle.

Let's fix the above issue:
abstract class MathematicalOperation {
abstract fun doCalculation(firstNumber: Int, secondNumber: Int): Int
}
class Addition : MathematicalOperation() {
override fun doCalculation(firstNumber: Int, secondNumber: Int): Int {
return firstNumber + secondNumber
}
}
class Subtraction : MathematicalOperation() {
override fun doCalculation(firstNumber: Int, secondNumber: Int): Int {
return firstNumber - secondNumber
}
}
class Multiplication : MathematicalOperation() {
override fun doCalculation(firstNumber: Int, secondNumber: Int): Int {
return firstNumber * secondNumber
}
}

Here in the above example, we are having an abstract class which is having the abstract method and all the subclasses are implementing its functionality according to the type of classes, So in the future, any new class will introduce so we need not modify existing code rather we can just define 1 class and its functionality and we are done.

3. Liskov substitution Principle:
Child classes should never break the parent class’ type definitions.
Parent classes should be easily substituted with their child classes without changing the behavior of the parent classes. It means that a subclass should override the methods from a parent class, which does not break the functionality of the parent class.
This becomes important when we use inheritance, We always need to check if the child class is a proper subset of a parent class then only we should use inheritance otherwise we are breaking LSP.
Let's see the bad example:
open class Bird {
fun fly() {}
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.