Skip to contentJMRG
All posts

SOLID Principles: The Foundation of Clean Architecture

solidclean-architecturedesign-principles

What is SOLID?

SOLID is an acronym representing five software design principles proposed by Robert C. Martin (Uncle Bob). These principles guide the creation of clean code and sustainable architectures, allowing us to build systems that are easy to maintain, extend, and test.

graph TB
    SOLID[SOLID Principles]
    S[S - Single Responsibility]
    O[O - Open/Closed]
    L[L - Liskov Substitution]
    I[I - Interface Segregation]
    D[D - Dependency Inversion]

    SOLID --> S
    SOLID --> O
    SOLID --> L
    SOLID --> I
    SOLID --> D

    S --> S1[One reason to change]
    O --> O1[Open for extension]
    O --> O2[Closed for modification]
    L --> L1[Substitutable subtypes]
    I --> I1[Specific interfaces]
    D --> D1[Depend on abstractions]

The 5 Principles

S - Single Responsibility Principle (SRP)

A class should have only one reason to change.

This principle states that each module or class should be responsible for only one part of the software's functionality.

Before: A class with multiple responsibilities

class UserService {
  createUser(userData: UserData): User { }
  sendWelcomeEmail(user: User): void { }
  generateReport(users: User[]): Report { }
  validateUserData(data: UserData): boolean { }
}

After: Each class has one responsibility

/** Repository for user persistence operations */
class UserRepository {
  create(userData: UserData): User { }
  findById(id: string): User | null { }
}
/** Email notification service */
class EmailService {
  sendWelcomeEmail(user: User): void { }
}
/** User data validator */
class UserValidator {
  validate(data: UserData): ValidationResult { }
}

O - Open/Closed Principle (OCP)

Software entities should be open for extension, but closed for modification.

We should be able to add new functionality without changing existing code.

Before: Needs modification for each new type

class PaymentProcessor {
  process(payment: Payment): void {
    if (payment.type === 'credit') {
      this.processCreditCard(payment);
    } else if (payment.type === 'paypal') {
      this.processPayPal(payment);
    }
  }
}

After: Extensible without modification

/** Payment strategy - base interface */
interface PaymentStrategy {
  process(amount: number): Promise<PaymentResult>;
}
class CreditCardPayment implements PaymentStrategy {
  async process(amount: number): Promise<PaymentResult> { }
}
class PayPalPayment implements PaymentStrategy {
  async process(amount: number): Promise<PaymentResult> { }
}
/** Processor that accepts any strategy */
class PaymentProcessor {
  constructor(private strategy: PaymentStrategy) {}
  async process(amount: number): Promise<PaymentResult> {
    return this.strategy.process(amount);
  }
}

L - Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types.

If S is a subtype of T, then objects of type T can be replaced by objects of type S without altering the program's properties.

Before: Square breaks Rectangle contract

class Rectangle {
  constructor(protected width: number, protected height: number) {}
  setWidth(width: number): void { this.width = width; }
  setHeight(height: number): void { this.height = height; }
  getArea(): number { return this.width * this.height; }
}
class Square extends Rectangle {
  setWidth(width: number): void {
    this.width = width;
    this.height = width;
  }
}

After: Appropriate interfaces for each shape

/** Base contract for geometric shapes */
interface Shape {
  getArea(): number;
}
class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  getArea(): number { return this.width * this.height; }
}
class Square implements Shape {
  constructor(private side: number) {}
  getArea(): number { return this.side * this.side; }
}

I - Interface Segregation Principle (ISP)

Specific interfaces are better than a general interface.

Clients should not be forced to depend on interfaces they don't use.

Before: Interface too large

interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
  attendMeeting(): void;
}

After: Segregated interfaces

interface Workable { work(): void; }
interface Eatable { eat(): void; }
interface Sleepable { sleep(): void; }
class Human implements Workable, Eatable, Sleepable {
  work(): void { }
  eat(): void { }
  sleep(): void { }
}
class Robot implements Workable {
  work(): void { }
}

D - Dependency Inversion Principle (DIP)

Depend on abstractions, not concretions.

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Before: High level depends on low level

class UserService {
  private database = new MySQLDatabase();
  getUser(id: string): User {
    return this.database.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}

After: Depends on abstractions

/** Database abstraction */
interface Database {
  query<T>(sql: string): T;
}
class UserService {
  constructor(private database: Database) {}
  getUser(id: string): User {
    return this.database.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}
const mysqlService = new UserService(new MySQLDatabase());
const postgresService = new UserService(new PostgresDatabase());
const mockService = new UserService(new MockDatabase());

Benefits of Applying SOLID

  1. Maintainability - Code easier to understand and modify
  2. Testability - Isolated components facilitate testing
  3. Extensibility - Add functionality without breaking existing code
  4. Reusability - More modular and reusable components
  5. Readability - Self-explanatory and well-organized code

Conclusion

SOLID principles are not rigid rules, but guides for making better design decisions. Applying them requires practice and judgment - sometimes a simpler design is preferable to one that follows SOLID to the letter.

In the next article we'll explore the Observer Pattern, one of the most used behavioral patterns in reactive and event-driven systems.


Based on principles by Robert C. Martin (Uncle Bob) and "Clean Architecture".