Command Palette

Search for a command to run...

Level 1 · 20 min

SOLID Principles

SOLID is a set of five principles that make object-oriented code more maintainable and extensible. They were popularized by Robert C. Martin (Uncle Bob) and remain the most widely cited OOP design guidelines.

SRP and OCP

Single Responsibility Principle: a class should have only one reason to change — one actor that could request a change. If a class handles both report generation and email sending, two different actors (finance vs IT ops) could require changes. Open/Closed Principle: software entities should be open for extension but closed for modification. Achieve new behavior by adding new classes (implementing an interface) rather than modifying existing working code.

LSP and ISP

Liskov Substitution Principle: subtypes must be behaviorally substitutable for their base types. A subclass must not weaken preconditions or strengthen postconditions — Square extending Rectangle violates LSP because a square cannot behave like a rectangle when width and height are set independently. Interface Segregation Principle: clients should not depend on interfaces they don't use — prefer many small, role-specific interfaces over one fat general-purpose interface. GoF introduces this principle implicitly throughout the catalog: "Program to an interface, not an implementation" (Gamma et al., Design Patterns, p.18). In Spring Framework, this manifests in the dozens of small, focused interfaces — BeanFactory, ApplicationContext, ResourceLoader — each exposing only the operations a specific client needs. A controller needing to publish events depends on ApplicationEventPublisher, not the full ApplicationContext, so it is not forced to depend on bean-loading or resource-resolution capabilities it will never use.

DIP

Dependency Inversion Principle: high-level modules should not depend on low-level modules; both should depend on abstractions. An OrderService should depend on a PaymentPort interface, not on a concrete StripePaymentGateway class. This is achieved via constructor injection — the concrete implementation is provided at runtime by the DI container.

Key Takeaways

  • SRP: one class, one reason to change. OCP: add behavior by adding code, not modifying existing code.
  • LSP: subclasses must be drop-in replacements — behavioral compatibility, not just syntactic.
  • DIP: depend on abstractions (interfaces), not concrete implementations. Inject dependencies.

Code example

// DIP violation: OrderService depends on concrete class
class OrderService {
  private StripeGateway stripe = new StripeGateway();
  void process(Order order) { stripe.charge(order.total()); }
}

// DIP applied: depend on interface, inject via constructor
interface PaymentGateway { void charge(Money amount); }

class OrderService {
  private final PaymentGateway gateway;
  OrderService(PaymentGateway gateway) { this.gateway = gateway; }
  void process(Order order) { gateway.charge(order.total()); }
}

// LSP violation: Square breaks Rectangle's contract
class Rectangle { void setWidth(int w); void setHeight(int h); }
class Square extends Rectangle {
  // VIOLATION: setting width must also set height
  void setWidth(int w) { this.width = w; this.height = w; }
}