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)