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 } }
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 */ } }
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.
✅ 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
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 }
✅ Good:
class Shape { area() { /* Abstract */ } } class Rectangle extends Shape { /* Implement area */ } class Square extends Shape { /* Implement area */ }
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!"); } }
✅ Good:
class Workable { work() { /* Interface */ } } class Eatable { eat() { /* Interface */ } } class Human implements Workable, Eatable { /* ... */ } class Robot implements Workable { /* ... */ }
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 } }
✅ Good:
class Database { save(data) { /* Abstract */ } } class MySQLDatabase extends Database { /* ... */ } class MongoDB extends Database { /* ... */ } class UserService { constructor(database) { this.db = database; // Injected dependency } }
Benefit: Decoupled, testable code.
Why SOLID Matters in JavaScript 🚀
Easier Maintenance: Changes affect fewer components.
Better Testability: Isolated logic is easier to test.
Flexible Architecture: Adapt to new requirements without rewrites.
Reusability: Components can be reused across projects.
Top comments (0)