S.O.L.I.D

Tharushi Dewangi
6 min readFeb 23, 2021

These five software development principles are the guidelines to be followed in software development and make it easy to scale and maintain. They were popularized by software engineer Robert C. By Martin.

●Single responsibility

●Open-close

●Liskov substitution

●Interface segregation

●Dependency inversion

Single responsibility

“A class should have one and only one reason to change, meaning that a class should have only one job”

Single responsibility

For example, consider an application that takes a collection of shapes — circles, and squares — and calculates area .we are arrange one class to one job. in circle class we calculate only circle area and in Square class we calculate only Square area

<script>
class shape
{
constructor(name) {
this.name=name;
}
}
class Square extends shape
{
constructor() {
super();
}
calculateSquareArea(length)
{
console.log("Square Area is "+length * length );
}

}
class Circle extends shape
{
constructor() {
super();
}
calculateCircleArea(radious)
{
console.log("Square Area is "+ radious *2*1.33 );
}

}
let s1 =new Square('A');
s1.calculateSquareArea(2);
let c1 =new Circle('A');
c1.calculateCircleArea(3);

</script>

Open-close

“Objects or entities should be open for extension, but closed for modification”

The design and writing of the code states that the open-closing principle states that the new functionality must be combined with the minimal changes in the existing code. The design should be done in such a way as to allow the new functionality to be added as new classes while keeping the existing code unchanged.

Software entities like classes, modules and functions should be open for extension but closed for modifications.

for example:

open/close from https://www.oodesign.com/open-close-principle.html
class Shape {
abstract void draw();
}

class Editor
{
public void drawShape(Shape s)
{
s.draw();
}
}
class Rectangle extends Shape {
public void draw() {

}
}

refer:https://www.oodesign.com/open-close-principle.html

Liskov substitution

“Every subclass/derived class should be able to substitute their parent/base class”

We always create a program module and we create a class hierarchy. We then extend the class length by creating several derivative classes.

We must ensure that the new derivative classes expand, without restoring the functionality of the old derivative classes. Otherwise new classes may have unwanted effects when used in existing program modules.

Lykov’s substitution principle states that if a program module uses an elementary class, it can be replaced with a derivative class that refers to the elementary class without any effect on the functionality of the program module.

Derived types must be completely substitutable for their base types.

class Bird {
fly(speed) {
return `Flying at ${speed} km/h`;
}
}

class Eagle extends Bird {
dive() {
// ...
}

fly(speed) {
return `Soaring through the sky at ${speed}`;
}
}

// LSP Violation:
class Penguin extends Bird {
fly() {
return new Error("Sorry, I cant fly");
}
}

Interface segregation

“Clients should not be forced to implement methods they do not use”

When we design an application, we need to consider how to make an abstract module consisting of several sub-modules. When considering a module implemented by a class, we can have a summary of the system performed on the interface. But if we want to extend our application by adding another module that contains only some sub-modules of the original system, we are forced to execute the entire interface and write some pseudo-modes. Such an interface is called a fat interface or a corrupt interface. Having interface corruption is not a good solution and can cause inappropriate behavior in the system.

The interface separation principle states that clients should not be forced to run interfaces that they do not use. Instead of a single fat interface, many smaller interfaces are more optimal based on the methodology, each serving one sub-module.

interface Vehicle {
make: string;
numberOfWheels: number;
maxSpeed?: number;
getReachKm(fuel: number, kmPerLitre: number): number;
}
class Car implements Vehicle {
make: string;
numberOfWheels: number;
maxSpeed: number;

constructor(make, numberOfWheels, maxSpeed) {
this.make = make;
this.numberOfWheels = numberOfWheels;
this.maxSpeed = maxSpeed;
}

getReachKm(fuel: number, kmPerLitre: number) {
return fuel * kmPerLitre;
}
}

const carObj = new Car("BMW", 4, 240);

Dependency inversion

When we design software applications, we may consider classes that implement basic and primary operations (disk access, network protocols, …) and advanced class complex logic (business streaming, …). The latter depend on the lower classes. A natural way of implementing such structures is to write low-level classes and complex high-level classes once we get them. This is the logical way to do it, because the upper classes are defined by others. But this is not a flexible design. What happens if we want to replace a lower class?

Let’s look at the classic example of a copy module that reads letters on a keyboard and writes them to a printing device. The upper class that contains logic is the copy class. The lower classes are KeyboardReader and PrinterWriter.

In a bad plan the upper class is used directly and relies heavily on the lower class. In such a case, if you want to change the design to transfer the output to a new file writer class, you must make changes to the copy class. (Suppose it is a very complex class, with many logics and difficult to test).

To avoid such problems we can introduce an abstract layer between the upper class and the lower class. High level modules contain complex logic and should not create a new abstract layer based on lower level modules as they do not depend on lower level modules. Low-level modules should be created based on the abstract layer.

The method of designing a class structure according to this principle is to start from the upper level modules to the lower level modules:
Upper level classes -> abstract layer -> lower level classes

import CustomHTTPClient from "CustomHTTPClient";

class WeatherProvider {
constructor(httpClient = CustomHTTPClient) {
this.httpClient = httpClient;
}

getWeather() {
return this.httpClient.get("");
}
}

references:

--

--