Hello Everyone,
I am pretty sure that you hear the term SOLID
all the time. Everyone seems to have an opinion about that. Mostly because there are lots of talks when it comes to such as abstract terms. But I want to deep dive into code to understand truly. Today I will talk about the Open/Closed Principle.
First thing first, what is SOLID. I take the below passage from Wikipedia.
In object-oriented computer programming, SOLID is a mnemonic 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, first introduced in his 2000 paper Design Principles and Design Patterns.
The SOLID concepts are:
- The Single-responsibility principle: "There should never be more than one reason for a class to change." In other words, every class should have only one responsibility.
- The Open–closed principle: "Software entities ... should be open for extension, but closed for modification."
- The Liskov substitution principle: "Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it".
- The Interface segregation principle: "Many client-specific interfaces are better than one general-purpose interface."
- The Dependency inversion principle: "Depend upon abstractions, [not] concretions." The SOLID acronym was introduced later, around 2004, by Michael Feathers.
Although the SOLID principles apply to any object-oriented design, they can also form a core philosophy for methodologies such as agile development or adaptive software development.
And here is my understanding :)
Single Responsibility: It states that entities/classes/functions should do only one thing. Therefore you will have only one reason to change your code. Less responsibility means less code, that means fewer bugs that means less mess to reason about ( Recursive error. Call stack size exceeded. Please help... Recursive error. Call stack size exceeded. Please help... ok ok I know bad joke :) ). There is no need to provide code samples for this. Usually, code samples for this are trivial and artificial. You got the idea I guess.
Open Closed Principle: Entities should be open to extension but close to modification. Waaaaaaaaaaaaw hang on, what? I know, I know I don't understand either. Let's dive into the code and understand truly.
Imagine we have a salary calculator function for a developer. If he/she has cloud skills we will pay $500 more. If he/she is not a developer, our function should return zero. First, let's write code in the traditional way.
const person = {
isDeveloper: true,
hasCloudSkill: true,
};
const calculateDeveloperSalary = (person) => {
if (!person.isDeveloper) {
return 0;
}
let salary = 3500;
if (person.hasCloudSkill) {
salary += 500;
}
return salary;
};
console.log(calculateDeveloperSalary(person)); // -> 4000
So far so good. Later on, we need to add a feature. The feature says that If a developer has Devops skills pay $500 more. In our existing codebase, our new version is like the below one.
const person = {
isDeveloper: true,
hasCloudSkill: true,
hasDevopsSkill: true,
};
const calculateDeveloperSalary = (person) => {
if (!person.isDeveloper) {
return 0;
}
let salary = 3500;
if (person.hasCloudSkill) {
salary += 500;
}
if (person.hasDevopsSkill) {
salary += 500;
}
return salary;
};
console.log(calculateDeveloperSalary(person)); // -> 4500
What actually happened here is that we need to add another if statement in our function. For every new feature, we will have to add another if/case ...etc That is not a good practice because we are modifying our base code. Instead, we should extend our code. When we modify our existing code, that can change existing behavior, break our code semantic. For avoiding these problems we will apply the Open/Closed Principle. Let's dive into code.
const isDeveloper = (person) => person.isDeveloper;
const hasCloudSkill = (person) => person.hasCloudSkill;
const devWithCloud = (p) => isDeveloper(p) && hasCloudSkill(p);
const notDeveloper = (p) => !isDeveloper(p);
const rest = (_) => true;
const devSkillStore = [
{ callback: notDeveloper, response: 0 },
{ callback: devWithCloud, response: 4000 },
{ callback: rest, response: 3500 },
];
const calculateDeveloperSalary = (person) => {
return devSkillStore.find((sk) => sk.callback(person)).response;
};
console.log(calculateDeveloperSalary(person)); // -> 4000
So far so good. We separated our functions which every one of them does only one thing(single responsibility) and then combine them to achieve what we want. But the real benefit we gain with this approach is extensibility. If we need to add a new feature we will not modify/change our existing function. We will just extend/add new capabilities. Let's implement DevOps skills implementation.
const isDeveloper = (person) => person.isDeveloper;
const hasDevopsSkill = (person) => person.hasDevopsSkill;
const hasCloudSkill = (person) => person.hasCloudSkill;
const devWithDevops = (p) => isDeveloper(p) && hasDevopsSkill(p);
const devWithCloud = (p) => isDeveloper(p) && hasCloudSkill(p);
const devNinja = (n) => devWithCloud(n) && hasDevopsSkill(n);
const notDeveloper = (p) => !isDeveloper(p);
const rest = (_) => true;
const devSkillStore = [
{ callback: notDeveloper, response: 0 },
{ callback: devNinja, response: 4500 },
{ callback: devWithCloud, response: 4000 },
{ callback: devWithDevops, response: 4000 },
{ callback: rest, response: 3500 },
];
const calculateDeveloperSalary = (person) => {
return devSkillStore.find((sk) => sk.callback(person)).response;
};
That is it. We add a new feature without modifying our existing function. That provides great flexibility and velocity in our software development journey. I will discuss other SOLID principles in upcoming articles with code examples. Stay tuned bye-bye.