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.