Understanding SOLID Principles in Javascript 2024

Amir Mustafa
6 min readMar 31, 2024

→ In today’s article we will understand SOLID principles simply.

→ This principle is valid for all programming languages. We will take the Javascript code as a reference.

→ SOLID principles were introduced by Robert C. “Uncle Bob” Martin, and serve as a foundational set of software design principles.

→ These principles offer guidance to developers, aiding in the creation of resilient and easily maintainable applications while reducing the expense associated with future modifications.

→ Despite their traditional association with object-oriented programming, SOLID principles can be effectively applied in languages beyond OOP paradigms, such as JavaScript.

→ This article aims to explore the utilization of SOLID principles in JavaScript, illustrating their application through practical code examples.

→ The full form of SOLID is:

S: Single Responsibility 
O: Open for extension, but closed to Modification
L: Liskov Substitution Principle (LSP)
I: Interface Seggregation Principle
D: Dependency Inversion Principle

Why SOLID principles are required?

→ This is because we keep code clean and according to standard. It makes code easily maintainable and is robust.

Understanding SOLID Principle:

1. Single Responsibility Principle i.e. SRP (S):

→ This principle is S of the SOLID principle.

→ Let us look at the below code example:

// WRONG
const validateAndCreateTask = (task, desc, email) => {
// Task 1: Validate Task Data
const isFormValid = testForm(task, desc, email);

if(FormValid) {
// Task 2: Insert Task in DB
Task.Create(task, desc, email)
}
}

→ Executing code wise this is correct. According to the SOLID principle, this is wrong

→ A single function is holding two tasks — validate and create

→ So Single Responsibility says, one function (or a method should hold) one task only

→ It is fine to call another function holding create task logic

// Correct
const validateTask = (req) => {
// Task 1: Validate Task Data
const isFormValid = testForm(task, desc, email);
if(FormValid) {
// Task 2: Calling another Function for Task 2
createTask(req)
}
}
// Task 2
createTask = (req) => Task.Create(req.task, req.desc, req.email)

→ So now the validate function holds the validate responsibility, and the createTask() function holds the creating task responsibility.

2. Open for extension, but closed to Modification i.e. OCP (O):

→ This rule of the SOLID principle says that whatever logic we implement should have the capability to add without deleting previous code or config.

→ This principle is O of the SOLID principle.

→ Let us check an example:

const db = ["ADMIN", "USER"];

checkRoles = (role) => {
if(db.includes(role)) {
return true;
} else {
return false;
}
}
// Testing
checkRoles("ADMIN"); //true
checkRoles("VALIDATOR"); //false

→ We see there are two roles supported i.e. ADMIN, and USER.

→ Now suppose we have to onboard a new role, we do not have to modify code. Simply adding a new role should be enough

// Now in future if requirement comes - new role introdiction
// We should built a capability of new role add - and not modify
const db = ["ADMIN", "USER"];

checkRoles = (role) => {
if(db.includes(role)) {
return true;
} else {
return false;
}
}
// New logic for extending a role
const addRole = (role) => {
db.push(role);
}
addRole("VALIDATOR"); // Validator role added
// Testing
checkRoles("ADMIN"); //true
checkRoles("VALIDATOR"); //true

→ Now we have added a new capability to add a role without deleting any previous code or data i.e. open for extension but not for modification.

3. Liskov Substitution Principle i.e. LSV (L):

→ This simply means that whatever method or functions are inherited should be done correctly as per their logic of class.

This principle states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program.

→ This principle is L of the SOLID principle.

→ Let us take a look at an example:

class Bird {
fly() {
// Fly Logic
}
}

class Eagle extends Bird {
dive() {
// Dive Logic
}
}
const eagle = new Eagle();
eagle.fly();
eagle.dive();

class Penguin extends Bird {
// PROBLEM: Penguin can't fly
}

→ Here we have a Bird class that has having fly() method.

→ Now two birds are inheriting it — Eagle and Penguin

→ With Eagle — It is fine. Eagle inherited the fly() method and has its own dive() method

→ With Penguin — As we know Penguins are flightless aquatic birds. So giving it fly method access is not correct.

→ So Liskov Principle says methods should be inherited without affecting the correctness of the program

→ Let us see the correct version of the code:

// Correct
// Penguin can dive, but cannot fly so we have to create functions - according to correctness - create more detailed functions

class Bird {
layEgg() {
// Lay Egg Logic
}
}
class FlyingBird {
fly() {
// Fly Logic
}
}
class SwimmingBird {
swim() {
// Swim Logic
}
}
class Eagle extends FlyingBird {}
class Penguin extends SwimmingBird {}
const penguin = new Penguin();
penguin.swim();

→ We know all birds lay eggs so that function in Bird Class. Now we have extended Birds based on their type — Flying or Swimming Bird

→ We extended Eagle from FlyingBird Class and Penguin from SwimmingBird Class.

→ The Eagle class did not get swim() method access and the Penguin class did not get fly() method access.

4. Interface Segregation Principle ISP (I):

→ This means before doing any action or API call we should check if it is required based on some flag.

→ Real world eg: If a vegetarian enters a restaurant. Giving him a non-veg menu card will be of no use.

→ In production-level applications lots of APIs are called according to application requirements. Now based on the flag we should check the API.

→ This principle is I of the SOLID principle.

→ Let us see a code:

// Wrong
class User {
constructor(username, password) {
this.username = username;
this.password = password;
this.initiateUser();
}
initiateUser() {
this.username = this.username;
this.validateUser();
}
validateUser(user, pass) {
console.log('validation in progress...');
}

}
const user = new User("John", "713300");

→ Here the worry is everything is chained and will execute.

→ Think heavy API logic is written. It will affect the performance of the application.

→ Instead, let us pass a flag to check.

// Correct
class UserISP {
constructor(username, password, validate) {
this.username = username;
this.password = password;
this.validate = validate;

if(validate) {
this.initiateUser();
} else {
console.log("no validation required...");
}
}
initiateUser() {
this.username = this.username;
this.validateUser();
}
validateUser(user, pass) {
console.log('validation in progress...');
}
}
const user = new User("John", "713300", false); // Validate will not execute
const user2 = new User("Amir", "713300", true); // Validate will execute

5. Dependency Inversion Principle DIP (D):

→ Dependency Inversion is a design principle in SOLID principles that says data should not be tightly coupled.

→ Example: if you are assigning some value, do not hard code it. Instead, use some function for doing it. So assigning data is dependent on the value we assign and not hardcoded directly.

→ This principle is D of the SOLID principle.

→ Let us see an example:

// Wrong
const httpRequest = axios.get('http://localhost:5000/api/eg', (data) => {
this.setState({ // Data are tightly coupled
name: data.name,
email: data.email,
password: data.password,
});
})

→ Let us see the improved code:

// Correct
const httpRequest = (url, setState) => setState.setValues(data);

const setState = {
setValues: (req, res) => {
this.setState({
name: req.name,
email: req.email,
password: req.password,
});
}
}
httpRequest('http://localhost:5000/api/eg', setState);

→ The idea is to make it reusable wherever possible. So that other code can use it as well if required.

Conclusion:

The SOLID principles are a set of five design principles in object-oriented or functional-oriented programming intended to make software designs more understandable, flexible, and maintainable.

Thank you for reading till the end 🙌 . If you enjoyed this article or learned something new, support me by clicking the share button below to reach more people and follow me on Twitter or subscribe Happy Learnings !! to see some other tips, articles, and things I learn about and share there.!

--

--

Amir Mustafa

JavaScript Specialist | Consultant | YouTuber 🎬. | AWS ☁️ | Docker 🐳 | Digital Nomad | Human. Connect with me on https://www.linkedin.com/in/amirmustafa1/