SOLID Principles of Object-Oriented Design
There’s a bunch of principles in object-oriented programming. One of them and probably the most popular is SOLID.
SOLID is an acronym for:
Single Responsibility Principle
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
The SOLID principles were coined by Robert C Martin (Uncle Bob).
Single Responsibility Principle
This is a principle that attempts to provide a solution on how and when to change an entity (function or class).
It states that there should never be more than one reason for an entity to change. Hence, every entity should have a single responsibility.
Do not add all possible features to a class. If the class has more than one functionality, then it will have multiple reasons to change.
In the previous article in this series (Building a CLI application), we added lots of functionality to the cli
function:
1. Fetching user details.
2. Modifying user details.
3. Billing the user.
4. Outputting to the console.
These multiple functionalities have given the cli
function multiple reasons to change and hence made the function less maintainable and less testable.
Open/Closed Principle
This principle connotes the idea of entities being closed for modification but should be open for extension.
What this means is you should try to write your code in such a way that you can add new features without modifying the existing code.
One way to achieve this is by using inheritance.
If you want to add new features to an existing class, you should not modify the class. Instead, you can create a subclass which will inherit from the class we intend on adding features to.
This new subclass will contain all the new features to be added to the class.
This way, other classes and entities that depended on the parent class will remain functional, while entities that need the new features can use the new class which still maintains the features of the other class.
Liskov Substitution Principle
The Liskov substitution principle was introduced by Barbara Liskov and it states: Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.
What this means is that all objects of a superclass should be replaceable with an object of a subclass without breaking the code.
Consider the following class:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
printAge(time: number): void {
setTimeout(() => {
console.log(this.age);
}, time);
}
}
And a subclass:
class Male extends Person{
constructor(name, age) {
super(name, age);
}
}
If we want to override the printAge
method of the Person
class in Male
, the new printAge
function should receive the same number of arguments, in this case, 1, and the arguments should have the same data type.
Both methods should also have the same return type, in this case, void.
class Male extends Person{
constructor(name, age) {
super(name, age);
}
printAge(time: number): void {
setTimeout(() => {
console.log(`Masculine age: ${this.age}`);
}, time);
}
}
Interface Segregation Principle
This principle states that classes should not implement any interface which it does not need.
Hence, instead of having one large interface with a bunch of methods, we should have several smaller interfaces and implement those them as required.
Dependency Inversion Principle
This principle connotes the idea that abstractions should not depend upon details, and high-level modules should not depend on low-level modules.
This principle aims at making us write code that can easily be decoupled.
If we are building a class to send HTTP requests, our class should not depend on a low-level module like Axios, but instead, we should build an abstraction over Axios and have our request class to depend on that abstraction.
// wrong
class HumanAPI {
getHumans() {
return axios.get('url')
}
}
// right
class HttpWrapper {
get(url) {
return axios.get(url)
}
}
class HumanAPI {
getHumans() {
return (new HttpWrapper()).get(url);;
}
}
This way, we can easily change from axios
to another HTTP library or fetch API
without modifying the HumanAPI
class.
Since our functionality will use the HumanAPI
class, we won’t need to deal with breaking code.
Conclusion
We have seen an overview of SOLID principles.
Next up will be to apply the SOLID principles to the CLI application built in the previous section.