SOLID Principles¶
SOLID is a set of 5 design principles for maintainable OOP code: S — Single Responsibility (one reason to change), O — Open/Closed (open for extension, closed for modification), L — Liskov Substitution (subtypes must be substitutable), I — Interface Segregation (prefer small, focused interfaces), D — Dependency Inversion (depend on abstractions, not concretions). Spring naturally encourages all five.
Single Responsibility Principle (SRP)¶
A class should have only one reason to change — it should do one thing and do it well. If a class handles user creation, email sending, AND reporting, a change to email logic could break user creation. Separate concerns into focused classes.
Deep Dive: SRP Example
Violation:
public class UserService {
public void createUser(User user) { ... }
public void sendWelcomeEmail(User user) { ... } // Email concern
public String generateReport(List<User> users) { ... } // Reporting concern
}
Fixed — each class has one responsibility:
public class UserService {
public void createUser(User user) { ... }
}
public class EmailService {
public void sendWelcomeEmail(User user) { ... }
}
public class ReportService {
public String generateReport(List<User> users) { ... }
}
In Spring: The @Service, @Repository, @Controller stereotype annotations naturally enforce SRP by separating business logic, data access, and web concerns.
Open/Closed Principle (OCP)¶
Classes should be open for extension but closed for modification. Instead of adding if-else branches for new behavior, use abstractions (interfaces/abstract classes) so new behavior is added by creating new implementations, without modifying existing code.
Deep Dive: OCP Example
Violation — must modify class for every new discount type:
public class DiscountCalculator {
public double calculate(String type, double price) {
if (type.equals("REGULAR")) return price * 0.1;
if (type.equals("PREMIUM")) return price * 0.2;
if (type.equals("VIP")) return price * 0.3; // Must modify class!
return 0;
}
}
Fixed — extend via abstraction (Strategy Pattern):
public interface DiscountStrategy {
double calculate(double price);
}
public class RegularDiscount implements DiscountStrategy {
public double calculate(double price) { return price * 0.1; }
}
public class PremiumDiscount implements DiscountStrategy {
public double calculate(double price) { return price * 0.2; }
}
// Adding VIP? Just create a new class — no modification needed
public class VipDiscount implements DiscountStrategy {
public double calculate(double price) { return price * 0.3; }
}
In Spring: @ConditionalOn* annotations, profiles, and DI-based strategy injection all follow OCP.
Liskov Substitution Principle (LSP)¶
Subtypes must be substitutable for their base types without altering program correctness. If substituting a subclass for a parent class breaks behavior, the inheritance hierarchy is wrong. Classic violation: Square extending Rectangle — setting width/height independently breaks the contract.
Deep Dive: LSP Violation — Square vs Rectangle
public class Rectangle {
protected int width, height;
public void setWidth(int w) { this.width = w; }
public void setHeight(int h) { this.height = h; }
public int area() { return width * height; }
}
public class Square extends Rectangle {
@Override
public void setWidth(int w) { this.width = w; this.height = w; }
@Override
public void setHeight(int h) { this.width = h; this.height = h; }
}
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(10);
assert r.area() == 50; // FAILS! area is 100 — LSP violated
Fix — don't use inheritance here:
Interface Segregation Principle (ISP)¶
No client should be forced to depend on methods it doesn't use. Prefer many small, focused interfaces to one large "fat" interface. If a Robot implements Worker but has to stub out eat() and sleep(), the interface is too broad — split it.
Deep Dive: ISP Example
Violation — fat interface:
public interface Worker {
void work();
void eat();
void sleep();
}
public class Robot implements Worker {
public void work() { ... }
public void eat() { /* Robots don't eat! */ } // Forced to implement
public void sleep() { /* Robots don't sleep! */ }
}
Fixed — segregated interfaces:
public interface Workable { void work(); }
public interface Feedable { void eat(); }
public interface Restable { void sleep(); }
public class Human implements Workable, Feedable, Restable { ... }
public class Robot implements Workable { ... } // Only what it needs
In Spring: Repository interfaces are focused — CrudRepository, PagingAndSortingRepository, JpaRepository let you choose the level of functionality you need.
Dependency Inversion Principle (DIP)¶
High-level modules should not depend on low-level modules — both should depend on abstractions. Code should depend on interfaces, not concrete classes. This is the theoretical foundation for Dependency Injection (DI) — Spring's core mechanism.
Deep Dive: DIP vs DI (Dependency Injection)
DIP is the principle — depend on abstractions.
DI is the mechanism — inject dependencies from outside rather than creating them.
Violation — direct dependency on concrete class:
public class OrderService {
private MySQLOrderRepository repository = new MySQLOrderRepository();
// Tightly coupled to MySQL — can't swap to Mongo without changing this
}
Fixed — depend on abstraction + inject:
public interface OrderRepository {
void save(Order order);
Optional<Order> findById(Long id);
}
public class MySQLOrderRepository implements OrderRepository { ... }
public class MongoOrderRepository implements OrderRepository { ... }
public class OrderService {
private final OrderRepository repository;
// Constructor injection — depends on abstraction
public OrderService(OrderRepository repository) {
this.repository = repository;
}
}
Spring does this automatically: @Autowired / constructor injection wires the concrete implementation at runtime.
SOLID in Spring¶
Deep Dive: How Spring Enforces SOLID
| Principle | Spring Support |
|---|---|
| SRP | @Service, @Repository, @Controller separate concerns |
| OCP | Strategy pattern via DI, @ConditionalOn* for extension |
| LSP | Interface-based programming, swappable implementations |
| ISP | Multiple focused repository/service interfaces |
| DIP | Constructor injection, program to interfaces |
Common Interview Questions¶
Common Interview Questions
- Explain each SOLID principle with an example.
- Give a real-world example where you applied SRP.
- How does the Open/Closed principle relate to the Strategy pattern?
- What is the Liskov Substitution Principle? Give a violation example.
- How does Spring Framework help enforce SOLID principles?
- What's the difference between DIP and DI (Dependency Injection)?