Low-level design2 min
Single Responsibility Principle
The Single Responsibility Principle (SRP) states that a class should have one, and only one, reason to change.
This means a class should only have one primary responsibility or job. If a class has multiple responsibilities, it becomes tightly coupled. A change to one responsibility might break the code related to the other responsibility.
Real-Life Analogy
Think of a Swiss Army Knife. It's great for camping because it has a knife, a screwdriver, a corkscrew, and scissors. However, if the scissors break, you have to send the entire tool in for repair, leaving you without your knife and screwdriver.
In a professional kitchen, a chef doesn't use a Swiss Army Knife. They use a dedicated Chef's Knife for chopping, and a dedicated Corkscrew for wine. Each tool has a Single Responsibility.
Example: Violation of SRP
Here is a User class that handles user data, but also handles database saving and sending emails. It has three reasons to change!
class User {
private name: string;
private email: string;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
// Reason to change 1: Business logic changes
public changeEmail(newEmail: string): void {
this.email = newEmail;
}
// Reason to change 2: Database schema or ORM changes
public saveToDatabase(): void {
console.log(`Saving ${this.name} to the database...`);
}
// Reason to change 3: Email provider (e.g. SendGrid) changes
public sendWelcomeEmail(): void {
console.log(`Sending welcome email to ${this.email}...`);
}
}
Example: Proper SRP Design
We refactor by splitting the responsibilities into three distinct classes:
// 1. Core Data (Only changes if user properties change)
class User {
public name: string;
public email: string;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
// 2. Database Logic (Only changes if DB changes)
class UserRepository {
public save(user: User): void {
console.log(`Saving ${user.name} to the database...`);
}
}
// 3. Email Logic (Only changes if Email provider changes)
class EmailService {
public sendWelcomeEmail(user: User): void {
console.log(`Sending welcome email to ${user.email}...`);
}
}
Why is it Important?
- Easier Maintenance: Bugs are isolated. Changing email logic won't accidentally break database logic.
- Better Testing: You can easily mock the
EmailServicewhen testing theUserRepository. - Readability: Classes stay small and focused.