SOLID Principle
Ebuka
Posted on December 16, 2022
Aside from writing code that passes the tests and returns the desired result, there is a concern for the code's longevity. Would the code help the product scale? Can you build incrementally without having to break your application?
SOLID is an acronym for the first five object-oriented design (OOD) principles by Robert C. Martin. Here is what SOLID means:
- S - Single Responsibility Principle (known as SRP)
- O - Open/Closed Principle
- L - Liskov’s Substitution Principle
- I - Interface Segregation Principle
- D - Dependency Inversion Principle
Let us try to understand each of these principles, one by one, using examples.
S - Single Responsibility Principle (known as SRP)
The name itself suggests that the class should have only one responsibility. What does it imply? So, consider class A, which performs the following operations:
- Create a database connection
- Retrieve data from a database
- Save the data to an external file
The issue with this class is that it handles so many operations. Suppose any of the following changes happen in the future:
- A brand-new database
- Use ORM to manage database queries
- Modifications to the output structure
As a result, changes to the above class would apply in all cases. These may have an impact on the execution of the other two operations as well. According to SRP, there should ideally be three classes, each with a single responsibility.
O - Open/Closed Principle
This principle suggests that classes should be open to extension but closed to modification. What does it imply? So, if John created a class A and Jane wants to modify it, she should be able to do so easily by extending the class rather than modifying it.
L - Liskov’s Substitution Principle
This principle suggests that parent classes should be easily interchangeable with their child classes without causing the application to crash. To illustrate, consider the following example:
Let us consider a vehicle parent class.
class Vehicle {
get wheels(): number {
return 4;
}
}
Now let us consider the Bike and Tricycle classes which extends Vehicle.
class Bike extends Vehicle {
get wheels(): number {
return 2;
}
}
class Tricycle extends Vehicle {
get wheels(): number {
return 3;
}
}
Now, wherever we used the Vehicle class in our code, we must be able to replace it with the Bike or Tricycle class without causing our application to crash. What this implies is that the child class should not implement code that causes the application to crash if it is replaced by the parent class.
I - Interface Segregation Principle
This principle suggests that many client specific interfaces are better than one general interface. Unlike other principles, this applies to interfaces. To better understand this principle, consider the following example.
OnClickListener and OnDoubleClickListener are two click listeners available in a sample service.
/**
* Interface definition for a callback to be invoked when a button is clicked.
*/
interface OnClickListener {
/**
* Called when a button has been clicked.
*
* @param b The button that was clicked.
*/
onClick: (b: Button) => void
}
/**
* Interface definition for a callback to be invoked when a button has been double clicked.
*/
interface OnLongClickListener {
/**
* Called when a button has been double clicked.
*
* @param v The view that was double clicked.
*
* @return true if the callback consumed the double click, false otherwise.
*/
onDoubleClick: (b: Button) => boolean;
}
Why do we need two interfaces to perform the same action with one tap and another with a double tap? Why can't we have the interface shown below?
interface MyOnClickListener {
onClick: (v: View) => void
onDoubleClick: (v: View) => boolean
}
If we have this interface, clients will be forced to implement onDoubleClick even if they don't care about tapping twice. This will result in the overhead of unused methods. Having two separate interfaces aids in the removal of unused methods. If a client requires both behaviors, they can implement both interfaces.
D - Dependency Inversion Principle
This principle suggest that classes should depend on abstraction but not on concretion. What does this mean? What this implies is that we hide the actual implementation of class A from class B. So, if class A changes, class B does not need to care or be aware of the changes.
In summary
- Single responsibility principle - Class has one job to do. Each change in requirements can be done by changing just one class.
- The open/closed principle states that the class is willing (open) to be used by others. Others are not welcome (closed) to change the class.
- The Liskov substitution principle states that any of a class's children can replace it. Children inherit their parents' behaviors.
- Interface segregation principle - When classes promise each other something, they should separate these promises (interfaces) into many small promises, so it's easier to understand.
- Dependency inversion principle - When classes talk to each other in a very specific way, they both depend on each other to never change. Classes should instead use promises (interfaces, parents) so that classes can change as long as the promise is kept.
Benefits
Adopting these practices will help you with writing better code, easy code refactoring, and practising Agile or Adaptive Software Development.
Posted on December 16, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.