JavaScript SOLID Principles: How to Write Maintainable Code
Tags: javascript, webdev, solidprinciples
Published: 2023-04-11 09:08:30

---
title: JavaScript SOLID Principles: How to Write Maintainable Code
published: true
date: 2023-04-10 11:30:00 UTC
tags: javascript, webdev, solidprinciples
canonical_url: https://www.syncfusion.com/blogs/post/solid-principles-in-javascript.aspx
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9f8tpyiae10nf2h3jfjv.png
---
SOLID principles are a set of software designs introduced by Robert C. “Uncle Bob” Martin. These principles guide developers in building robust, maintainable applications while minimizing the cost of changes.

Although SOLID principles are often used with object-oriented programming, we can use them with other languages like JavaScript. In this article, we will discuss how to use SOLID principles in JavaScript and demonstrate them with code examples.

## What are the SOLID principles?

- [**S**ingle responsibility principle](#single)
- [**O**pen-closed principle](#open)
- [**L**iskov substitution principle](#liskov)
- [**I**nterface segregation principle](#interface)
- [**D**ependency inversion principle](#dependency)

### <a name="single">Single responsibility principle</a>

**A class, a module, or a function should be only responsible for one actor. So, it should have one and only one reason to change.**

The single responsibility principle is one of SOLID’s simplest principles. However, developers often misinterpret it, thinking a module should do a single thing.

Let’s consider a simple example to understand this principle. The following JavaScript code snippet has a class named  **ManageEmployee**  and several functions to manage employees.

```js
class ManageEmployee {

  constructor(private http: HttpClient)
  SERVER_URL = 'http://localhost:5000/employee';

  getEmployee (empId){
     return this.http.get(this.SERVER_URL + `/${empId}`);
  }

  updateEmployee (employee){
     return this.http.put(this.SERVER_URL + `/${employee.id}`,employee);
  }

  deleteEmployee (empId){
     return this.http.delete(this.SERVER_URL + `/${empId}`);
  }

  calculateEmployeeSalary (empId, workingHours){
    var employee = this.http.get(this.SERVER_URL + `/${empId}`);
    return employee.rate * workingHours;
  }

}
```

The previous code seems completely fine at a glance, and many developers will follow the same approach without any issues. However, since it is responsible for two actors, this class violates the single responsibility principle. The **getEmployee()**, **updateEmployee()**, and **deleteEmployee()** functions are directly associated with HR management, while the **calculateEmployeeSalary()** function is associated with finance management.

In the future, if you need to update a function for the HR or finance department, you will have to change the **ManageEmployee** class, affecting both actors. Therefore, the **ManageEmployee** class violates the single responsibility principle. You would need to separate the functionalities related to the HR and finance departments to make the code compatible with the single responsibility principle. The following code example demonstrates this.

```js
class ManageEmployee {

  constructor(private http: HttpClient)
  SERVER_URL = 'http://localhost:5000/employee';

  getEmployee (empId){
     return this.http.get(this.SERVER_URL + `/${empId}`);
  }

  updateEmployee (employee){
     return this.http.put(this.SERVER_URL + `/${employee.id}`,employee);
  }

  deleteEmployee (empId){
     return this.http.delete(this.SERVER_URL + `/${empId}`);
  }

}

class ManageSalaries {

  constructor(private http: HttpClient)
  SERVER_URL = 'http://localhost:5000/employee';

  calculateEmployeeSalary (empId, workingHours){
    var employee = this.http.get(this.SERVER_URL + `/${empId}`);
    return employee.rate * workingHours;
  }

}

```
### <a name="open">Open-closed principle</a>

**Functions, modules, and classes should be extensible but not modifiable.**

This is an important principle to follow when implementing large-scale applications. According to this principle, we should be able to add new features to applications easily, but we should not introduce breaking changes to the existing code.

For example, assume that we have implemented a function named **calculateSalaries()** that uses an array with defined job roles and hourly rates to calculate salaries.

```js
class ManageSalaries {
  constructor() {
    this.salaryRates = [
      { id: 1, role: 'developer', rate: 100 },
      { id: 2, role: 'architect', rate: 200 },
      { id: 3, role: 'manager', rate: 300 },
    ];
  }

  calculateSalaries(empId, hoursWorked) {
    let salaryObject = this.salaryRates.find((o) => o.id === empId);
    return hoursWorked * salaryObject.rate;
  }
}

const mgtSalary = new ManageSalaries();
console.log("Salary : ", mgtSalary.calculateSalaries(1, 100));
```

![Output before implementing Open closed principle](https://www.syncfusion.com/blogs/wp-content/uploads/2023/04/Output-before-implementing-Open-closed-principle.png)

Directly modifying the **salaryRates** array will violate the open-closed principle. For example, suppose you need to extend the salary calculations for a new role. In that case, you need to create a separate method to add salary rates to the **salaryRates** array without making to the original code.

```js
class ManageSalaries {
  constructor() {
    this.salaryRates = [
      { id: 1, role: 'developer', rate: 100 },
      { id: 2, role: 'architect', rate: 200 },
      { id: 3, role: 'manager', rate: 300 },
    ];
  }

  calculateSalaries(empId, hoursWorked) {
    let salaryObject = this.salaryRates.find((o) => o.id === empId);
    return hoursWorked * salaryObject.rate;
  }

  addSalaryRate(id, role, rate) {
    this.salaryRates.push({ id: id, role: role, rate: rate });
  }
}

const mgtSalary = new ManageSalaries();
mgtSalary.addSalaryRate(4, 'developer', 250);
console.log('Salary : ', mgtSalary.calculateSalaries(4, 100));

```

![Output after implementing Open-closed principle](https://www.syncfusion.com/blogs/wp-content/uploads/2023/04/Output-after-implementing-Open-closed-principle.png)

### <a name="liskov">Liskov substitution principle</a>

**Let P(_y_) be a property provable about objects _y_ of type A. Then P(_x_) should be true for objects _x_ of type B where B is a subtype of A.**

You will find different definitions of the Liskov substitution principle across the internet, but they all imply the same meaning. In simple terms, the Liskov principle states that we should not replace a parent class with its subclasses if they create unexpected behaviors in the application.

For example, consider a class named **Animal,** which includes a function named **eat()**.

```js
class Animal{
  eat() {
    console.log("Animal Eats")
  }
}

```

Now I will extend the **Animal** class to a new class named **Bird** with a function named **fly()**.

```js
class Bird extends Animal{
  fly() {
    console.log("Bird Flies")
  }
}

var parrot = new Bird();
parrot.eat();
parrot.fly();

```

![Output before implementing Liskov Substitution Principle](https://www.syncfusion.com/blogs/wp-content/uploads/2023/04/Output-before-implementing-Liskov-Substitution-Principle.png)

In the previous example, I have created an object named **parrot** from the **Bird** class and called both the **eat()** and **fly()** methods. Since the **parrot** is capable of both those actions, extending the **Animal** class to the **Bird** class does not violate the Liskov principle.

Now let’s extend the **Bird** class further and create a new class named **Ostrich.**

```js
class Ostrich extends Bird{
  console.log("Ostriches Do Not Fly")
}

var ostrich = new Ostrich();
ostrich.eat();
ostrich.fly();

```

This extension of the **Bird** class violates the Liskov principle since Ostriches cannot fly—this could create unexpected behavior in the application. The best way to address this case is to extend the **Ostrich** class from the **Animal** class.

```js
class Ostrich extends Animal{

  walk() {
    console.log("Ostrich Walks")
  }

}

```

![Output after implementing Liskov Substitution principle](https://www.syncfusion.com/blogs/wp-content/uploads/2023/04/Output-after-implementing-Liskov-Substitution-principle.png)

### <a name="interface">Interface segregation principle</a>

**Clients should not be pushed to depend on interfaces they will never use.**

This principle is related to interfaces and focuses on breaking large interfaces into smaller ones. For example, suppose you are going to driving school to learn how to drive a car, and they give you a large set of instructions on driving cars, trucks, and trains. Since you only need to learn to drive a car, you do not need all the other information. The driving school should divide the instructions and just give you the instructions specific to cars.

Since JavaScript does not support interfaces, it is difficult to adopt this principle in JavaScript-based applications. However, we can use JavaScript compositions to implement this. Compositions allow developers to add functionalities to a class without inheriting the entire class. For example, assume that there is a class named **DrivingTest** with two functions named **startCarTest** and **startTruckTest.** If we extend the **DrivingTest** class for **CarDrivingTest** and **TruckDrivingTest,** we have to force both classes to implement the **startCarTest** and **startTruckTest** functions.

```js
Class DrivingTest {
  constructor(userType) {
    this.userType = userType;
  }

  startCarTest() {
    console.log(“This is for Car Drivers”’);
  }

  startTruckTest() {
    console.log(“This is for Truck Drivers”);
  }
}

class CarDrivingTest extends DrivingTest {
  constructor(userType) {
    super(userType);
  }

  startCarTest() {
    return “Car Test Started”;
  }

  startTruckTest() {
    return null;
  }
}

class TruckDrivingTest extends DrivingTest {
  constructor(userType) {
    super(userType);
  }

  startCarTest() {
    return null;
  }

  startTruckTest() {
    return “Truck Test Started”;
  }
}

const carTest = new CarDrivingTest(carDriver );
console.log(carTest.startCarTest());
console.log(carTest.startTruckTest());

const truckTest = new TruckDrivingTest( ruckdriver );
console.log(truckTest.startCarTest());
console.log(truckTest.startTruckTest());

```

![Output before implementing Interface Segregation Principle](https://www.syncfusion.com/blogs/wp-content/uploads/2023/04/Output-before-implementing-Interface-Segregation-Principle.png)

However, this implementation violates the interface segregation principle since we are forcing both extended classes to implement both functionalities. We can resolve this by using compositions to attach functionalities for required classes as shown in the following sample.

```js
Class DrivingTest {
  constructor(userType) {
    this.userType = userType;
  }
}

class CarDrivingTest extends DrivingTest {
  constructor(userType) {
    super(userType);
  }
}

class TruckDrivingTest extends DrivingTest {
  constructor(userType) {
    super(userType);
  }
}

const carUserTests = {
  startCarTest() {
    return ‘Car Test Started’;
  },
};

const truckUserTests = {
  startTruckTest() {
    return ‘Truck Test Started’;
  },
};

Object.assign(CarDrivingTest.prototype, carUserTests);
Object.assign(TruckDrivingTest.prototype, truckUserTests);

const carTest = new CarDrivingTest(carDriver );
console.log(carTest.startCarTest());
console.log(carTest.startTruckTest()); // Will throw an exception

const truckTest = new TruckDrivingTest( ruckdriver );
console.log(truckTest.startTruckTest());
console.log(truckTest.startCarTest()); // Will throw an exception

```

![Output after implementing Interface Segregation Principle](https://www.syncfusion.com/blogs/wp-content/uploads/2023/04/Output-after-implementing-Interface-Segregation-Principle.png)

Now, **carTest.startTruckTest();** will throw an exception since the **startTruckTest()** function is not assigned to the **CarDrivingTest** class.

### <a name="dependency">Dependency inversion principle</a>

**Higher-level modules should use abstractions. However, they should not depend on low-level modules.**

Dependency inversion is all about decupling your code. Following this principle will give you the flexibility to scale and change your application at the highest levels without any issues.

Regarding JavaScript, we do not need to think about abstractions since JavaScript is a dynamic language. However, we need to make sure higher-level modules do not depend on lower-level modules.

Let’s consider a simple example to explain how dependency inversion works. Suppose you used the Yahoo email API in your application, and now you need to change it to the Gmail API. If you implemented the controller without dependency inversion like the following sample, you need to make some changes to the controller. This is because multiple controllers use the Yahoo API and you need to find each instance and update it.

```js
class EmailController { 
  sendEmail(emailDetails) { 
    // Need to change this line in every controller that uses YahooAPI.const response = YahooAPI.sendEmail(emailDetails); 
    if (response.status == 200) { 
       return true;
    } else {
       return false;
    }
  }
}

```

The dependency inversion principle helps developers avoid such costly mistakes by moving the email API handling part to a separate controller in this case. Then you only need to change that controller whenever there is a change in the email API.

```js
class EmailController { 
  sendEmail(emailDetails) { 
    const response = EmailApiController.sendEmail(emailDetails);   
    if (response.status == 200) { 
       return true;
    } else {
       return false;
    }
  }
}

class EmailApiController {
  sendEmail(emailDetails) {
    // Only need to change this controller. return YahooAPI.sendEmail(emailDetails);
  }
}

```

## Conclusion

In this article, we discussed the importance of SOLID principles in software design and how we can adopt these concepts in JavaScript applications. As developers, it is essential to understand and use these core concepts in our applications. Sometimes the benefits of these principles may not be obvious when working with small applications, but you will surely know the difference they make once you start to work on a large-scale project.

I hope this article helped you to understand SOLID principles. Thank you for reading.

The Syncfusion [JavaScript suite](https://www.syncfusion.com/javascript-ui-controls "Javascript UI controls")is the only suite you will ever need to build an application. It contains over 80 high-performance, lightweight, modular, and responsive UI components in a single package. Download the [free trial](https://www.syncfusion.com/downloads/essential-js2 "Free evaluation of the Essential Studio products") and evaluate the controls today.

If you have any questions or comments, you can contact us through our [support forums](https://www.syncfusion.com/forums "Syncfusion Support Forum"), [support portal](https://support.syncfusion.com/ "Syncfusion Support Portal"), or [feedback portal](https://www.syncfusion.com/feedback/ "Syncfusion Feedback Portal"). We are always happy to assist you!

## Recommended resources

- [SOLID Principles Succinctly](https://www.syncfusion.com/succinctly-free-ebooks/solidprinciplessuccinctly "E-book: SOLID Principles Succinctly")

## Related blogs

- [Top 8 JavaScript Notification Libraries](https://www.syncfusion.com/blogs/post/top-8-javascript-notification-libraries.aspx "Blog: Top 8 JavaScript Notification Libraries")
- [What’s New in Essential JS 2: 2023 Volume 1](https://www.syncfusion.com/blogs/post/essential-js-2-2023-volume-1.aspx "Blog: What’s New in Essential JS 2: 2023 Volume 1")
- [Syncfusion Essential Studio 2023 Volume 1 Is Here!](https://www.syncfusion.com/blogs/post/essential-studio-2023-volume-1.aspx "Blog: Syncfusion Essential Studio 2023 Volume 1 Is Here!")
- [Integrating JavaScript Scheduler with PHP and MySQL for Dynamic CRUD Operations](https://www.syncfusion.com/blogs/post/javascript-scheduler-with-php-and-mysql-for-dynamic-crud-operations.aspx "Blog: Integrating JavaScript Scheduler with PHP and MySQL for Dynamic CRUD Operations")