--- id: solid-srp-class title: SOLID - Single Responsibility Principle (Class Level) category: solid-principles priority: critical tags: [SOLID, SRP, single-responsibility, class-design] related: [solid-srp-function, core-separation-concerns, solid-isp-interfaces] --- # Single Responsibility Principle - Class Level A class should have only one reason to change, meaning it should have only one job or responsibility. ## Bad Example ```typescript // Anti-pattern: Class handles multiple responsibilities class UserManager { private db: Database; private mailer: EmailService; constructor() { this.db = new Database(); this.mailer = new EmailService(); } createUser(userData: UserData): User { // Validation logic if (!userData.email.includes('@')) { throw new Error('Invalid email'); } if (userData.password.length < 8) { throw new Error('Password too short'); } // Database logic const user = this.db.insert('users', userData); // Email logic const htmlContent = `

Welcome ${user.name}!

`; this.mailer.send(user.email, 'Welcome', htmlContent); // Logging logic console.log(`User created: ${user.id} at ${new Date()}`); this.writeToFile(`/logs/users.log`, `Created: ${user.id}`); return user; } private writeToFile(path: string, content: string): void { // File writing implementation } } ``` ## Good Example ```typescript // Correct approach: Each class has a single responsibility // Handles user validation only class UserValidator { validate(userData: UserData): ValidationResult { const errors: string[] = []; if (!userData.email.includes('@')) { errors.push('Invalid email format'); } if (userData.password.length < 8) { errors.push('Password must be at least 8 characters'); } return { isValid: errors.length === 0, errors }; } } // Handles user persistence only class UserRepository { constructor(private db: Database) {} save(userData: UserData): User { return this.db.insert('users', userData); } findById(id: string): User | null { return this.db.findOne('users', { id }); } } // Handles welcome email only class WelcomeEmailService { constructor(private mailer: EmailService) {} sendWelcomeEmail(user: User): void { const htmlContent = this.buildTemplate(user); this.mailer.send(user.email, 'Welcome', htmlContent); } private buildTemplate(user: User): string { return `

Welcome ${user.name}!

`; } } // Handles user activity logging only class UserActivityLogger { constructor(private logger: Logger) {} logCreation(user: User): void { this.logger.info(`User created: ${user.id}`, { userId: user.id }); } } // Orchestrates the user creation process class UserService { constructor( private validator: UserValidator, private repository: UserRepository, private welcomeEmail: WelcomeEmailService, private activityLogger: UserActivityLogger ) {} createUser(userData: UserData): User { const validation = this.validator.validate(userData); if (!validation.isValid) { throw new ValidationError(validation.errors); } const user = this.repository.save(userData); this.welcomeEmail.sendWelcomeEmail(user); this.activityLogger.logCreation(user); return user; } } ``` ## Why 1. **Easier Testing**: Each class can be unit tested in isolation without mocking unrelated dependencies. 2. **Reduced Coupling**: Changes to email templates don't affect database logic or validation rules. 3. **Better Reusability**: The `UserValidator` can be reused for profile updates, the `WelcomeEmailService` can be triggered from different flows. 4. **Clearer Ownership**: Teams can own specific classes without stepping on each other's work. 5. **Simpler Maintenance**: Bug in email formatting? Look only at `WelcomeEmailService`. Validation issue? Check `UserValidator`. 6. **Flexible Composition**: Easy to add features like async email sending or different logging strategies without touching core logic.