7.6 SOLID Principles
SOLID is an acronym for five core principles of object-oriented design coined by Robert C. Martin ("Uncle Bob"). Following these principles produces code that is resilient to change, easy to understand, and highly reusable.
S - Single Responsibility Principle
"A class should have only one reason to change."
// ❌ SRP Violation: one class handles everything
class User {
String name, email;
void saveToDatabase() { /* DB logic */ }
void sendWelcomeEmail() { /* Email logic */ }
String formatUserInfo() { return name + " <" + email + ">"; }
}
// ✅ SRP Compliant: separate responsibilities
class User { String name, email; }
class UserRepository { void save(User u) { /* DB logic */ } }
class EmailService { void sendWelcome(User u) { /* Email logic */ } }
class UserFormatter { String format(User u) { return u.name + " <" + u.email + ">"; } }
O - Open/Closed Principle
"Open for extension, closed for modification."
// ❌ OCP Violation: adding a new type requires modifying existing code
class DiscountCalculator {
double calculate(String type, double price) {
if (type.equals("VIP")) return price * 0.2;
if (type.equals("STUDENT")) return price * 0.1;
return 0; // Adding new types requires editing here!
}
}
// ✅ OCP Compliant: extend by adding new classes
interface DiscountPolicy { double calculate(double price); }
class VipDiscount implements DiscountPolicy { public double calculate(double p) { return p * 0.2; } }
class StudentDiscount implements DiscountPolicy { public double calculate(double p) { return p * 0.1; } }
class SeniorDiscount implements DiscountPolicy { public double calculate(double p) { return p * 0.15; } }
class OrderService {
double finalPrice(double price, DiscountPolicy policy) {
return price - policy.calculate(price);
}
}
L - Liskov Substitution Principle
"Subtypes must be substitutable for their base types."
// ❌ LSP Violation: Square inheriting Rectangle breaks behavior
class Rectangle {
protected int width, height;
void setWidth(int w) { this.width = w; }
void setHeight(int h) { this.height = h; }
int area() { return width * height; }
}
class Square extends Rectangle {
@Override void setWidth(int w) { this.width = this.height = w; }
@Override void setHeight(int h) { this.width = this.height = h; }
}
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(10);
System.out.println(r.area()); // Expected: 50, Actual: 100 ← LSP violation!
// ✅ LSP Compliant: use a common interface
interface Shape { int area(); }
class Rectangle implements Shape { /* ... */ }
class Square implements Shape { /* ... */ }
I - Interface Segregation Principle
"Clients should not be forced to depend on interfaces they don't use."
// ❌ ISP Violation: robots must implement eat() and sleep()
interface Worker { void work(); void eat(); void sleep(); }
class RobotWorker implements Worker {
public void work() { System.out.println("Robot working."); }
public void eat() { throw new UnsupportedOperationException("Robots don't eat"); }
public void sleep() { throw new UnsupportedOperationException("Robots don't sleep"); }
}
// ✅ ISP Compliant: split by responsibility
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }
class HumanWorker implements Workable, Eatable, Sleepable { /* all three */ }
class RobotWorker implements Workable { public void work() { System.out.println("Robot working."); } }
D - Dependency Inversion Principle
"Depend on abstractions, not concretions."
// ❌ DIP Violation: depends on concrete class
class UserService {
private MySQLDatabase db = new MySQLDatabase(); // Directly depends on implementation!
void saveUser(String user) { db.save(user); }
}
// ✅ DIP Compliant: depend on interface
interface Database { void save(String data); }
class MySQLDatabase implements Database { public void save(String d) { System.out.println("MySQL: " + d); } }
class PostgreSQLDatabase implements Database { public void save(String d) { System.out.println("PostgreSQL: " + d); } }
class UserService {
private final Database db;
UserService(Database db) { this.db = db; } // Constructor injection (DI)
void saveUser(String user) { db.save(user); }
}
// External injection - Spring does this automatically!
UserService service = new UserService(new MySQLDatabase());
Pro Tip
When to apply SOLID:
SOLID proves its worth as projects grow. Refactor when you see these signals:
- SRP: More than one reason to modify a class → split it
- OCP: Adding features requires editing existing classes → add abstractions
- LSP: Overridden methods throw exceptions or have empty implementations → reconsider inheritance
- ISP: Implementing classes littered with
UnsupportedOperationException→ split interfaces - DIP:
new ConcreteClass()everywhere → use interfaces + dependency injection
In practice, Spring's @Autowired, @Service, @Repository handle DIP and DI automatically.