SOLID Principles

S - Single Responibility Principle
O - Open/Closed Principle
L - Liskov Substitution Principle
I - Interface Segmented Principle
D - Dependency Inversion Principle

Advantages of following the solid principles, Help us to write better code -
1. avoid duplicate code
2. easy to maintain code
3. easy to understand
4. flexible software
5. reduce complexity

S - Single Responibility Principle

A class should have only one Responibility and one reason to change.

        
// Without adhering to SRP
class Report {
  constructor(title, content) {
    this.title = title;
    this.content = content;
  }

  generateReport() {
    console.log(`Generating report for ${this.title}:\n${this.content}`);
  }

  saveToDatabase() {
    console.log(`Saving report to the database: ${this.title}`);
  }
}

// With SRP
class Report {
  constructor(title, content) {
    this.title = title;
    this.content = content;
  }

  generateReport() {
    console.log(`Generating report for ${this.title}:\n${this.content}`);
  }
}

class ReportSaver {
  saveToDatabase(report) {
    console.log(`Saving report to the database: ${report.title}`);
  }
}

// Usage
const report = new Report("Monthly Report", "This is the content of the report.");
report.generateReport();

const reportSaver = new ReportSaver();
reportSaver.saveToDatabase(report);
        
      

O - Open/Closed Principle

Open for extension but Closed for modification

        
// Without adhering to OCP

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  calculateArea() {
    return this.width * this.height;
  }
}

// Now, imagine there's a requirement to add support for calculating the perimeter.

// Without OCP, you might be tempted to modify the existing class:
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  calculateArea() {
    return this.width * this.height;
  }

  calculatePerimeter() {
    return 2 * (this.width + this.height);
  }
}

// With OCP

// Instead, you can create an interface or abstract class
class Shape {
  calculateArea() {
    throw new Error("Method not implemented");
  }
}

// Extend the Shape class with the specific implementation (Rectangle)
class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  calculateArea() {
    return this.width * this.height;
  }
}

// Now, you can easily add new shapes without modifying existing code
class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  calculateArea() {
    return Math.PI * Math.pow(this.radius, 2);
  }
}

        
      

L - Liskov Substitution Principle

If class B is a subtype of class A, then we should be able to replace object of A with B without breaking the behavior of the program

        
// Without adhering to LSP

class Bird {
  fly() {
    console.log("Flying...");
  }
}

class Penguin extends Bird {
  // Penguins cannot fly, but we are violating LSP by not providing a fly method
}

// Usage

function makeBirdFly(bird) {
  bird.fly();
}

const bird = new Bird();
const penguin = new Penguin();

makeBirdFly(bird);    // Outputs: Flying...
makeBirdFly(penguin); // Error or unexpected behavior because penguins can't fly

// Adhering to LSP

class Bird {
  fly() {
    console.log("Flying...");
  }
}

class Penguin extends Bird {
  fly() {
    console.log("Penguins can't fly!");
  }
}

// Usage

function makeBirdFly(bird) {
  bird.fly();
}

const bird = new Bird();
const penguin = new Penguin();

makeBirdFly(bird);    // Outputs: Flying...
makeBirdFly(penguin); // Outputs: Penguins can't fly!

        
      

I - Interface Segmented Principle

Interfaces should be such, that client should not implement unnecessary functions they do not need.

        
// Without adhering to ISP

// A monolithic interface with methods for both printing and scanning
class MultiFunctionDevice {
  print() {
    console.log("Printing...");
  }

  scan() {
    console.log("Scanning...");
  }
}

// Classes that implement the monolithic interface
class Printer extends MultiFunctionDevice {}

class Scanner extends MultiFunctionDevice {}

// Usage

const printer = new Printer();
const scanner = new Scanner();

printer.print(); // Outputs: Printing...
printer.scan();  // Outputs: Scanning...

scanner.print(); // Outputs: Printing... (Unexpected behavior for a scanner)
scanner.scan();  // Outputs: Scanning...

// Adhering to ISP

// Separate interfaces for printing and scanning
class Printer {
  print() {
    console.log("Printing...");
  }
}

class Scanner {
  scan() {
    console.log("Scanning...");
  }
}

// Usage

const printer = new Printer();
const scanner = new Scanner();

printer.print(); // Outputs: Printing...

scanner.scan();  // Outputs: Scanning...
// scanner.print(); // Error - 'print' is not a function for a Scanner

        
      

D - Dependency Inversion Principle

Class should depend on interface rather than concrete classes.

        
// Without DIP

class Switch {
  turnOn() {
    console.log("Device turned on");
  }

  turnOff() {
    console.log("Device turned off");
  }
}

class Fan extends Switch {
  // Fan-specific functionality
}

// Usage

const fanSwitch = new Fan();
fanSwitch.turnOn();  // Outputs: Device turned on
fanSwitch.turnOff(); // Outputs: Device turned off
-------
// Adhering to DIP

// Abstraction
class Switchable {
  turnOn() {
    throw new Error("Method not implemented");
  }

  turnOff() {
    throw new Error("Method not implemented");
  }
}

// Low-level module
class Switch extends Switchable {
  turnOn() {
    console.log("Device turned on");
  }

  turnOff() {
    console.log("Device turned off");
  }
}

// High-level module
class Fan {
  constructor(device) {
    this.device = device;
  }

  operate() {
    this.device.turnOn();
    // Additional fan-specific logic here
    this.device.turnOff();
  }
}

// Usage

const switchableDevice = new Switch();
const fan = new Fan(switchableDevice);

fan.operate();  // Outputs: Device turned on, Device turned off

        
      

Video Resource