Why SOLID Matters
1. Single Responsibility Principle (SRP)
❌ Bad Example — Violates SRP
class UserService {
void register(User user) {
validate(user);
saveToDB(user);
sendWelcomeEmail(user);
}
void validate(User user) { }
void saveToDB(User user) { }
void sendWelcomeEmail(User user) { }
}
This class has multiple responsibilities → validation, persistence, and email sending.
✔ Good Example — Follows SRP
class UserValidator {
void validate(User user) { }
}
class UserRepository {
void save(User user) { }
}
class EmailService {
void sendWelcomeEmail(User user) { }
}
class UserService {
private final UserValidator validator = new UserValidator();
private final UserRepository repository = new UserRepository();
private final EmailService emailService = new EmailService();
void register(User user) {
validator.validate(user);
repository.save(user);
emailService.sendWelcomeEmail(user);
}
}
Each class now has one responsibility.
2. Open/Closed Principle (OCP)
❌ Bad Example — Requires modifying code for each new payment type
class PaymentService {
void pay(String type) {
if (type.equals("credit")) {
// pay with credit card
} else if (type.equals("paypal")) {
// pay with PayPal
}
}
}
Adding ApplePay means adding another if → violation of OCP.
✔ Good Example — Open for extension, closed for modification
interface PaymentMethod {
void pay();
}
class CreditCardPayment implements PaymentMethod {
public void pay() { System.out.println("Pay with credit card"); }
}
class PayPalPayment implements PaymentMethod {
public void pay() { System.out.println("Pay with PayPal"); }
}
class PaymentService {
private final PaymentMethod method;
PaymentService(PaymentMethod method) {
this.method = method;
}
void pay() {
method.pay();
}
}
To add ApplePay, you only create a new class — no old code changes.
3. Liskov Substitution Principle (LSP)
❌ Bad Example — Violates LSP
class Bird {
void fly() { }
}
class Penguin extends Bird {
@Override
void fly() {
throw new RuntimeException("Penguins can't fly");
}
}
A Penguin cannot be substituted for a Bird that is expected to fly.
✔ Good Example — Proper abstraction
class Bird { }
class FlyingBird extends Bird {
void fly() { }
}
class Eagle extends FlyingBird {
@Override
void fly() { System.out.println("Eagle flying"); }
}
class Penguin extends Bird {
void swim() { System.out.println("Penguin swimming"); }
}
Subclasses now behave logically.
4. Interface Segregation Principle (ISP)
❌ Bad Example — Too large interface
interface Worker {
void work();
void eat();
void sleep();
}
class Robot implements Worker {
public void work() { }
public void eat() { } // meaningless
public void sleep() { } // meaningless
}
Robot is forced to implement irrelevant methods.
✔ Good Example — Split into smaller interfaces
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }
class Human implements Workable, Eatable, Sleepable {
public void work() { }
public void eat() { }
public void sleep() { }
}
class Robot implements Workable {
public void work() { }
}
Each class implements only what it needs.
5. Dependency Inversion Principle (DIP)
❌ Bad Example — High-level depends on low-level
class EmailSender {
void send(String msg) { }
}
class Notification {
private EmailSender email = new EmailSender();
void send(String msg) {
email.send(msg);
}
}
✔ Good Example — Depend on abstraction
interface MessageSender {
void send(String msg);
}
class EmailSender implements MessageSender {
public void send(String msg) { }
}
class SmsSender implements MessageSender {
public void send(String msg) { }
}
class Notification {
private final MessageSender sender;
Notification(MessageSender sender) {
this.sender = sender;
}
void send(String msg) {
sender.send(msg);
}
}
The high-level module (Notification) now depends only on an interface.