DEV Community

Carlos Azaustre
Carlos Azaustre

Posted on • Originally published at carlosazaustre.es

SOLID Principles in JavaScript

TL;DR:

Prefers video content? Here are the 5 SOLID programming principles explained, applied to JavaScript (in Spanish)

Introduction to SOLID - What They Are and Why They Matter

The SOLID principles are an acronym of five design values:

  1. Single Responsibility Principle (S)
  2. Open-Closed Principle (O)
  3. Liskov Substitution Principle (L)
  4. Interface Segregation Principle (I)
  5. Dependency Inversion Principle (D)

We'll delve into each of these principles applying them to our favorite programming language: JavaScript. We begin with the first principle.

Single Responsibility Principle (S)

According to this principle, a class should have only one reason to change, that is, it should have only one task or responsibility.

To illustrate this principle, let’s think of an orchestra. Each musician has an instrument they know how to play perfectly. The violinist knows how to play the violin, but if we also put them to play the drum, surely something would not work well.

Applying this example to programming, each class or function should have a single responsibility. This simplifies understanding, maintenance, and modification of the code in the future.



function calculateSalary(employee) {
    let salary = employee.hoursWorked * employee.hourlyRate;
    let report = /*...*/;
    console.log(report);
    return salary;
}


Enter fullscreen mode Exit fullscreen mode

Example provided with a calculateSalary() function, which is refactored to adhere to the single responsibility principle.



function calculateSalary(employee) {
    return employee.hoursWorked * employee.hourlyRate;
}

function generateReport(employee, salary) {
    let report = /*...*/;
    console.log(report);
}


Enter fullscreen mode Exit fullscreen mode

Open-Closed Principle (O)

The second SOLID principle is the open-closed principle. Its official definition holds that software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.



function processPayment(price, cardDetails) {
    /*...*/
    console.log('Payed with Card.');
}


Enter fullscreen mode Exit fullscreen mode

Example provided with a processPayment() function and how it can be extended to handle different payment methods without modifying the existing code.



function processPaymentWithPayPal(price, accountDetails) {
    /*...*/
    console.log('Payed with PayPal.');
}


Enter fullscreen mode Exit fullscreen mode

Liskov Substitution Principle (L)

The Liskov substitution principle states that "objects of a superclass should be replaceable by objects of a subclass without affecting the correctness of the program".



// example function to make HTTP requests
function makeRequest(url, errorHandler) {
    fetch(url)
        .then(response => response.json())
        .catch(error => errorHandler.handle(error))
    }

// We can have several functions to handle errors
const consoleErrorHandler = function handle(error){
    console.log(error)
}

const externalErrorHandler = function handle(error){
    sendErrorToExternalService(error)
}

makeRequest(url, consoleErrorHandler);
makeRequest(url, externalErrorHandler);


Enter fullscreen mode Exit fullscreen mode

Example provided with a makeRequest() function and how different error handler objects can be used interchangeably without causing errors in the program.

Interface Segregation Principle (I)

The fourth SOLID principle, "I," refers to the Interface Segregation Principle. Based on this principle, no class should be forced to implement interfaces or methods that it will not use.

It's better to have specific interfaces, rather than a single general interface. And this also applies to functions in JavaScript.



class Product {
    constructor() { /* */ }

    getDetails() { /* */ }
    saveToDb() {/* */ }
    displayInFrontEnd() { /* */ }
}

class DigitalProduct extends Product{
    // saveToDb() is extended but not necessary }
}

// ---- //
// Refactor

class Product {
    constructor() { /* */ }

    getDetails() { /* */ }
    displayInFrontEnd() { /* */ }
}

class PhysicalProduct extends Product {
    constructor() {
        super()
    }
    saveToDb() { /* */ }
}

class DigitalProduct extends Product{
    // saveToDb() is not extended
}


Enter fullscreen mode Exit fullscreen mode

Example provided with a Product class, illustrating the violation of this principle when a DigitalProduct class extends Product but doesn't need one of its methods. The example then shows the refactoring following this principle to have a PhysicalProduct class extending Product where the unnecessary method is moved.

Dependency Inversion Principle (D)

The last principle, "D," is the Dependency Inversion Principle. This principle holds that high-level modules, i.e., the modules that contain strategic decisions and high-level directives, should not depend on low-level modules, which are the modules that contain detailed and low-level logic.

Both, high-level and low-level modules, should depend on abstractions.



class MySqlConnection {
    connect() { /* */ }
}

class PasswordReminder {
    constructor() {
        this.dbConnection = new MySQLConnection();
    }
}

// Refactor
class MySqlConnection {
    connect() { /* */ }
}
class PostgreSqlConnection {
    connect() { /* */ }
}

class PasswordReminder {
    constructor(connection) {
        this.dbConnection = connection
    }
}


Enter fullscreen mode Exit fullscreen mode

Example provided with a PasswordReminder class initially depending directly on a MySQLConnection class. The refactoring follows the principle by having PasswordReminder receive an instance of a class implementing a common "database connection" interface, showing both MySQLConnection and PostgreSqlConnection classes.

Final Thoughts

SOLID rules are a very useful tool for programming. However, like any tool, they should not be applied blindly in all cases. The advice is to follow these principles where it makes sense and can help you maintain and improve your code in the long term.

"Software is a cycle in which you are developing code, new features or functionalities are needed, bugs appear unintentionally or intentionally, and you will need to refactor. You don't necessarily always have to be writing new code. Most of your time you will be reading code from others or even yours and you will be able to see that there are parts that you can refactor to improve maintenance and even the performance of your application."

See you next time!

You have a spanish version of this article here

Top comments (0)