DEV Community

Cover image for πŸ› οΈ SOLID Principles in JavaScript: Write Better Code with Examples
Alaa Samy
Alaa Samy

Posted on

7

πŸ› οΈ SOLID Principles in JavaScript: Write Better Code with Examples

SOLID principles are a set of design guidelines that help you create maintainable, flexible, and scalable code.

Let’s break them down with JavaScript examples:


1. Single Responsibility Principle (SRP)

A class/function should have only one reason to change.

❌ Bad:

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  saveToDatabase() {
    // Database logic here
  }

  sendEmail() {
    // Email logic here
  }
}

Enter fullscreen mode Exit fullscreen mode

Problem: The User class handles both data management and email logic.

βœ…Good:

class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

class UserRepository {
  saveToDatabase(user) { /* DB logic */ }
}

class EmailService {
  sendEmail(user) { /* Email logic */ }
}
Enter fullscreen mode Exit fullscreen mode

Benefit: Each class has a single responsibility.


2. Open/Closed Principle (OCP)

Entities should be open for extension but closed for modification.

❌ Bad:

class Logger {
  logToConsole(message) {
    console.log(message);
  }

  logToFile(message) {
    // Write to file
  }
}

// To add a new logger (e.g., HTTP), you must modify the Logger class.
Enter fullscreen mode Exit fullscreen mode

βœ… Good:

// Use a strategy pattern to extend behavior
class Logger {
  log(message, loggerStrategy) {
    loggerStrategy(message);
  }
}

// Define strategies (extensions)
const consoleStrategy = (msg) => console.log(msg);
const fileStrategy = (msg) => writeToFile(msg);
const httpStrategy = (msg) => fetch('/log', { body: msg }); // New logger added without changing Logger class

// Usage:
const logger = new Logger();
logger.log("Error!", httpStrategy); // No need to modify Logger
Enter fullscreen mode Exit fullscreen mode

Benefit: Extend functionality without altering existing code.


3. Liskov Substitution Principle (LSP)

Subclasses should replace their parent class without breaking functionality.

❌ Bad:

class Rectangle {
  setWidth(w) { this.width = w }
  setHeight(h) { this.height = h }
}

class Square extends Rectangle {
  setSize(size) { // Violates LSP
    this.width = size;
    this.height = size;
  }
}

function resizeShape(shape) {
  shape.setWidth(10);
  shape.setHeight(5); // Breaks for Square
}
Enter fullscreen mode Exit fullscreen mode

βœ… Good:

class Shape {
  area() { /* Abstract */ }
}

class Rectangle extends Shape { /* Implement area */ }
class Square extends Shape { /* Implement area */ }
Enter fullscreen mode Exit fullscreen mode

Benefit: Subclasses don’t break parent class behavior.


4. Interface Segregation Principle (ISP)

Clients shouldn’t depend on interfaces they don’t use.

❌ Bad:

class Worker {
  work() { /* ... */ }
  eat() { /* ... */ }
}

// Robot forced to implement eat()
class Robot extends Worker {
  eat() { throw Error("Robots don't eat!"); }
}
Enter fullscreen mode Exit fullscreen mode

βœ… Good:

class Workable {
  work() { /* Interface */ }
}

class Eatable {
  eat() { /* Interface */ }
}

class Human implements Workable, Eatable { /* ... */ }
class Robot implements Workable { /* ... */ }
Enter fullscreen mode Exit fullscreen mode

Benefit: Avoid bloated interfaces.


5. Dependency Inversion Principle (DIP)

Depend on abstractions (interfaces), not concretions (specific implementations).

❌ Bad:

class MySQLDatabase {
  save(data) { /* MySQL-specific */ }
}

class UserService {
  constructor() {
    this.db = new MySQLDatabase(); // Tight coupling
  }
}
Enter fullscreen mode Exit fullscreen mode

βœ… Good:

class Database {
  save(data) { /* Abstract */ }
}

class MySQLDatabase extends Database { /* ... */ }
class MongoDB extends Database { /* ... */ }

class UserService {
  constructor(database) {
    this.db = database; // Injected dependency
  }
}
Enter fullscreen mode Exit fullscreen mode

Benefit: Decoupled, testable code.


Why SOLID Matters in JavaScript πŸš€

  1. Easier Maintenance: Changes affect fewer components.

  2. Better Testability: Isolated logic is easier to test.

  3. Flexible Architecture: Adapt to new requirements without rewrites.

  4. Reusability: Components can be reused across projects.

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

πŸ‘‹ Kindness is contagious

If you found this article helpful, please give a ❀️ or share a friendly comment!

Got it