DEV Community

Cover image for 🛠️ SOLID Principles in JavaScript: Write Better Code with Examples
Alaa Samy
Alaa Samy

Posted on

🛠️ 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.

Top comments (0)