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.
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; }
}