SOLID Design Principles — Simplified

Nidhin Narayanan Nair
5 min readNov 23, 2021

Design principles are widely applicable laws, guidelines, and design considerations that should be followed to create an efficient system. They can be used in any language or platform. Below are the S.O.L.I.D design principles which are the most popular ones.

Single-responsibility principle

The single responsibility principle states each class will be responsible for performing one single purpose. This means that a class will do only one job and should have only one reason to change. Let’s explain with the below example.

export class Student {

constructor () {}

private addNewStudent(){
// Add new student code
}

private deleteStudent(){
// Delete student code
}

private admitStudentToSchool(){
// Admit the student to the school code
}

}

In the above class Student, there are three methods out of which addNewStudent and deleteStudent are related to the student but admitStudentToSchool is related to the process of admitting the student to the school. By this, the above example is a violation of the Single responsibility principle.

Open-Closed Principle

OCP states that any entities such as classes, modules, functions, etc. should be open for extension, but closed for modification. Any new functionality should be implemented by adding new classes and methods instead of modifying the existing ones. Let’s see the below example to explain OCP.

Consider the Employee class which calculates the Bonus of the employees.

export class Employee {
constructor () {}
private calculateBonus(salary){
return salary * 0.1;
}
}

Later, the company started hiring contract employees who are not eligible for bonuses. For handling this scenario, we will have to pass the employee type and modify the calculateBonus function accordingly. By doing this it violates the open-closed principle.

For making this OCP compliant, the Employee class can be modified as an abstract class and leave the implantation of the bonus to the derived classes. This helps in extending the class for further enhancement without modifying the base class.

If this principle is not followed, we end up in the below-mentioned trouble.

· End up testing the entire functionality

· Costly process for the organization

· Breaks the single responsibility principle as well.

· Maintaining the class becomes an overhead.

Liskov Substitution Principle

LSP is an extension of the Open/Closed Principle. It states

· Derived types must be completely substituted by their subtypes.

· No new exceptions can be thrown by the subtype.

· New derived classes just extend without replacing the functionality of the old class.

· Liskov Substitution principle is a particular definition of a subtyping relation.

Let’s understand LSP with an example.

In mathematics, a Square is a type of Rectangle. So, let’s model this with inheritance. So, in order to make a Square, you need to inherit the Rectangle class.

Now, imagine you have methods setWidth() and setHeight() in your Rectangle class. But when Square is using the same Rectangle base class, these methods do not make sense as setting one will change the other to match it. In this case, Square violates the Liskov Substitution Principle.

Interface Segregation Principle

As per the Interface Segregation Principle (ISP),

· No client should be forced to depend on methods it does not use.

· One fat interface should be split into many smaller interfaces.

Let’s take the below example to understand more.

Consider an Interface that is used to perform printing tasks.

public interface IPrintTasks{
bool PrintContent(string content);
bool ScanContent(string content);
bool FaxContent(string content);
bool PhotoCopyContent(string content);
}

The HP LaserJet printer class is implementing the interface IPrintTasks interface for performing its printing tasks. So far so good. But now Canon printer is introduced and is extending the same IPrintTasks interface for performing its printing tasks. But Canon printer does not have the Fax feature. But still, it needs to implement all the methods in the IPrintTasks interface. This is a violation of the Interface Segregation Principle.

As per ISP, this interface needs to be broken down into smaller interfaces like below.

public interface IPrintScanContent{
bool PrintContent(string content);
bool ScanContent(string content);
bool PhotoCopyContent(string content);
}

public interface IFaxContent{
bool FaxContent(string content);
}

Now, these interfaces can be implemented as per the need.

Dependency Inversion Principle

DIP is about the coupling between different classes or modules. It focuses on an approach where the higher classes are not dependent on the lower classes instead depend on the abstraction of the lower class.

Let’s demonstrate with the below example.

Consider an online store that has payment APIs integrated.

PayPal will be exposing APIs for performing the actions like initiating payment, cancellation, refund, etc.

We have integrated those APIs into our application and are working perfectly. But now we must integrate Google pay also as another payment option. As per the above implementation, we will have to rewrite the Online shop application code a lot to integrate the Google Pay APIs to perform similar actions. This becomes a mess when more and more payment options are getting integrated.

To avoid this, we can introduce something kind of an interface in the middle which will look like below.

The Payment processor exposes different methods like startPayment, cancelPayment, etc. which our online shop application will be invoking. The Payment Processor is a kind of an adapter class that handles different Payment options and invokes the corresponding APIs. Whenever a new payment option is to be introduced, we just need to add that in the Payment processor and the online store application doesn’t need any modification as it will be invoking the same methods of Payment processor class.

In this way, our online shop application is not dependent on the low-level implementations which are PayPal and GPay

--

--