SOLID principles

A circular table SOLID principlesa globe on top of it

The SOLID principles are a set of five design principles intended to make software designs more understandable, flexible, and maintainable. Below are the principles with concise explanations and examples:

1. Single Responsibility Principle (SRP)

  • Definition: A class should have only one reason to change, meaning it should have only one responsibility or job.

Example:

// Bad example
class UserService {
  createUser() { /* logic for creating user */ }
  sendEmail() { /* logic for sending email */ }
}

// Good example
class UserService {
  createUser() { /* logic for creating user */ }
}

class EmailService {
  sendEmail() { /* logic for sending email */ }
}

2. Open/Closed Principle (OCP)

  • Definition: Software entities (classes, modules, functions) should be open for extension but closed for modification.

Example:

// Bad example: Modifying existing class for adding new features
class Shape {
  draw() { /* logic for drawing a shape */ }
}

// Good example: Extending class without modifying original code
interface Shape {
  draw(): void;
}

class Circle implements Shape {
  draw() { /* logic for drawing a circle */ }
}

class Rectangle implements Shape {
  draw() { /* logic for drawing a rectangle */ }
}

3. Liskov Substitution Principle (LSP)

  • Definition: Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.

Example:

// Bad example
class Bird {
fly() { /* logic for flying */ }
}

class Ostrich extends Bird {
fly() { throw new Error("Ostriches can't fly"); }
}

// Good example
class Bird {
// common bird behavior
}

class FlyingBird extends Bird {
fly() { /* logic for flying */ }
}

class Ostrich extends Bird {
// logic for ostrich behavior without flying
}

4. Interface Segregation Principle (ISP)

  • Definition: A client should not be forced to implement an interface it doesn’t use. Instead of one large interface, create smaller, more specific interfaces.

Example:

// Bad example
interface Animal {
  fly(): void;
  swim(): void;
}

class Dog implements Animal {
  fly() { throw new Error("Dogs can't fly"); }
  swim() { /* logic for swimming */ }
}

// Good example
interface Swimmable {
  swim(): void;
}

interface Flyable {
  fly(): void;
}

class Dog implements Swimmable {
  swim() { /* logic for swimming */ }
}

Dependency Inversion Principle (DIP)

  • Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details, details should depend on abstractions.

Example:

// Bad example
class MySQLDatabase {
  connect() { /* MySQL connection logic */ }
}

class UserService {
  db: MySQLDatabase;
  constructor() {
    this.db = new MySQLDatabase();
  }
}

// Good example
interface Database {
  connect(): void;
}

class MySQLDatabase implements Database {
  connect() { /* MySQL connection logic */ }
}

class UserService {
  db: Database;
  constructor(db: Database) {
    this.db = db;
  }
}

These examples illustrate how applying the SOLID principles helps in creating more robust, maintainable, and scalable software systems.