S.O.L.I.D
Fundamental OOP Principles

In object-orientated programming, SOLID is an acronym for five design principles intended to make software designs more understandable, flexible and maintainable. The principles are a subset of many principles promoted by American software engineer and instructor Robert C. Martin.
The intention of these principles is to make software designs more understandable, easier to maintain and easier to extend.
Below are the five design principles:
S — Single responsibility principle
O — Open/closed principle
L — Liskov substitution principle
I — Interface segregation principle
D — Dependency inversion principle
Let’s get into more detail for each one of the principles!
S — Single responsibility principle
A class should have one, and only one, reason to change.
The single responsibility principle is a computer programming principle that states that every class or function should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class, module or function. The argument for the single responsibility principle is relatively simple: it makes your software easier to implement and prevents unexpected side-effects of future changes.
Benefits:
- Testing — A class with one responsibility will have far fewer test cases
- Lower coupling — Less functionality in a single class will have fewer dependencies
- Organisation — Smaller, well-organised classes are easier to search than monolithic ones
For example, let’s think of a class such as below:
class Person {
constructor(name: string){ }
getPersonName() { }
savePerson(a: Person) { }
}
The above class violated the Single Responsibility Principle. We can see that the class has two responsibilities: 1. person properties management, 2. person database management.
If our application changes in a way that it affects the database management functions can cause issues in the future. The classes that make use of Person properties will have to be written again and recompiled to compensate for the new changes in order to maintain functionality.
Solution:
We could refactor our class Person, so that it can be written better and seperate the two responsibilities, one sole responsibility for the management of the person properties and another responsibility for the person database management.
class Person {
constructor(name: string){ }
getPersonName() { }
}class PersonFromDB {
getPerson(a: Person) { }
savePerson(a: Person) { }
}
O — Open/closed principle
You should be able to extend a class’s behaviour, without modifying it.
This principle is the foundation for building code that is maintainable and reusable.
Robert C. Martin
This principle follows two criteria:
- Open for extension
This ensures that the class behaviour can be extended. As requirements change, we should be able to make a class behave in new and different ways, to meet the needs of the new requirements.
- Closed for modification
The source code of such a class is set in stone, no one is allowed to make changes to the code.
For example,
Let’s imagine you have a e-commerce shop, and you give a offer of 20% to your loyal customers using this class:
class Offer {
giveOffer() {
return this.price * 0.2
}
}
When you decide to offer double the 20% discount to returning customers. You may modify the class like this:
class Offer {
giveOffer() {
if(this.customer == 'loyal') {
return this.price * 0.2;
}
if(this.customer == 'returning') {
return this.price * 0.4;
}
}
}
Unfortunately, this fails the Open/Closed Principle. If we want to give a new percent offer to a different type of customer, we will have to add a new logic .
We will add a new class that will extend the Offer. In this new class, we would implement its new behaviour:
class ReturningOffer: Offer {
getOffer() {
return super.getOffer() * 2;
}
}
L — Liskov substitution principle
Derived classes must be substitutable for their base classes.
What is wanted here is something like the following substitution property: If
for each object o1 of type S there is an object o2 of type T such that for all
programs P defined in terms of T, the behaviour of P is unchanged when o1 is
substituted for o2 then S is a subtype of T.Barbara Liskov
Simply put, if class A is a subtype of class B, then we should be able to replace B with A without disrupting the behaviour of our program.
To ensure that your code follows the Liskov Substitution Principle. In the best case, you do this via code reviews and test cases. In your test cases, you can execute a specific part of your application with objects of all subclasses to make sure that none of them causes an error or significantly changes its performance.
I — Interface segregation principle
In programming, the interface segregation principle states that no client should be forced to depend on methods it does not use.
Simply put: Do not add additional functionality to an existing interface by adding new methods, but create a new interface and let your class implement multiple interfaces if needed.
In other words, larger interfaces should be split into smaller ones. This will ensure that implementing classes only need to be concerned about the methods that are of interest to them.
D — Dependency inversion principle
Based on this idea, Robert C. Martin’s definition of the Dependency Inversion Principle consists of two parts:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
An important detail of this definition is, that high-level and low-level modules depend on the abstraction. The design principle does not just change the direction of the dependency, as you might have expected when you read its name for the first time. It splits the dependency between the high-level and low-level modules by introducing an abstraction between them. So in the end, you get two dependencies:
- the high-level module depends on the abstraction, and
- the low-level depends on the same abstraction.
By following these principles, we can achieve a more modular, scalable and easy to maintain codebase.