Skip to main content

SOLID Principles

Practical application of SOLID principles based on Martin Fowler's guidance

Single Responsibility Principle (SRP)

Principle: Each class/module should have one reason to change.

Application

  • Related to Beck's "reveals intention" - focused classes are easier to understand
  • A class should have only one job or responsibility
  • If a class has multiple reasons to change, split it into separate classes

Warning Signs (Code Smells)

  • Large Class - Too many fields/methods/lines of code
  • Divergent Change - One class changes for multiple different reasons
  • Shotgun Surgery - One change requires modifications across many classes

Example

Violates SRP:

class UserManager {
validateUser()
saveToDatabase()
sendEmail()
generateReport()
}
```text

✅ **Follows SRP:**
```text
class UserValidator { validateUser() }
class UserRepository { saveToDatabase() }
class EmailService { sendEmail() }
class ReportGenerator { generateReport() }
```text

## Open/Closed Principle (OCP)

**Principle:** Software entities should be open for extension, closed for modification.

### Application

- Use polymorphism to organize calculations by type
- Prefer composition and interfaces over modification of existing code
- Add new functionality by adding new code, not changing existing code
- Related to refactoring flow: organize by type using polymorphism

### Implementation Strategies

- Abstract base classes or interfaces
- Strategy pattern for varying algorithms
- Template method pattern for varying steps
- Dependency injection for swappable implementations

## Liskov Substitution Principle (LSP)

**Principle:** Subtypes must be substitutable for their base types without breaking correctness.

### Application

- Ensure polymorphic behavior is predictable and consistent
- Derived classes should honor the contracts established by base classes
- Don't break behavioral expectations of calling code
- Don't weaken preconditions or strengthen postconditions

### Violation Examples

- Subclass throws exceptions not thrown by base class
- Subclass changes behavior in unexpected ways
- Subclass removes functionality present in base class

## Interface Segregation Principle (ISP)

**Principle:** Clients shouldn't depend on interfaces they don't use.

### Application

- Create focused, cohesive interfaces
- Related to Beck's "fewest elements" - remove unnecessary dependencies
- Split large interfaces into smaller, specific ones
- Prevents "fat interfaces" that force implementations to define unused methods

### Benefits

- Reduces coupling between components
- Makes code more flexible and easier to refactor
- Improves testability by minimizing dependencies

## Dependency Inversion Principle (DIP)

**Principle:** Depend on abstractions, not concretions.

### Core Rules

1. High-level modules shouldn't depend on low-level modules
2. Both should depend on abstractions (interfaces/protocols)
3. Abstractions shouldn't depend on details
4. Details should depend on abstractions

### Application

- Enables testability through test doubles
- Use interfaces to define contracts between components
- Inject dependencies rather than creating them directly
- Aligns with Dependency Injection principle

### Example

❌ **Violates DIP:**
```text
class OrderProcessor {
private database = new MySQLDatabase(); // Concrete dependency

process() {
this.database.save();
}
}
```text

✅ **Follows DIP:**
```text
interface Database {
save();
}

class OrderProcessor {
constructor(private database: Database) {} // Abstract dependency

process() {
this.database.save();
}
}
```text

## SOLID Principles Summary

When applied together, SOLID principles create:

- Code that's easier to understand and maintain
- Systems that are flexible and adaptable to change
- Components that are testable and mockable
- Architecture that supports growth without major rewrites
- Clear boundaries and responsibilities

Remember: These are guidelines, not absolute rules. Apply them pragmatically based on context and balance them against other design concerns.