1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: SOLID = 5 design principles for maintainable OO code. (S)RP = class has one reason to change, (O)CP = open for extension, closed for modification, (L)SP = subtypes must be substitutable, (I)SP = small focused interfaces, (D)IP = depend on abstractions, not concretions.
- Benefits: Maintainable, testable, flexible code.
- Violations: God classes, fragile base class, fat interfaces, tight coupling.
- Key: SOLID = guidelines, not rules. Balance with simplicity!
Think of building construction. SRP = electrician does wiring, plumber does pipes (one job each). OCP = add rooms without demolishing foundation. LSP = any plug fits any outlet (substitutable). ISP = specialized tools (saw vs hammer) not one super-tool. DIP = blueprint (abstraction) before building (concrete)!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- SRP: Chef cooks, waiter serves (separate responsibilities)
- OCP: Smartphone (add apps without changing OS)
- LSP: Universal remote (works with any TV)
- ISP: Screwdriver set (specific tools, not Swiss Army knife for everything)
- DIP: Contract (agree on interface, not implementation)
3. Technical Mastery (The "Deep Dive")
SOLID Principles Overview
| Principle | Acronym | Core Idea | Violation Example |
|---|---|---|---|
| Single Responsibility | SRP | One reason to change | God class |
| Open/Closed | OCP | Extend, don't modify | if-else chains |
| Liskov Substitution | LSP | Subtypes substitutable | Square extends Rectangle |
| Interface Segregation | ISP | Small, focused interfaces | Fat interface |
| Dependency Inversion | DIP | Depend on abstractions | new ConcreteClass() |
4. Interactive & Applied Code
java
// ===========================================
// 1. SINGLE RESPONSIBILITY PRINCIPLE (SRP)
// ===========================================
// ❌ BAD: Multiple responsibilities (God class)
class UserService {
void createUser(User user) {
// Validate user
if (user.email == null) throw new Exception("Invalid email");
// Save to database
Connection conn = DriverManager.getConnection("...");
// SQL insert...
// Send welcome email
EmailClient client = new EmailClient();
client.send(user.email, "Welcome!");
// Log activity
System.out.println("User created: " + user.name);
}
}
// Problem: 4 reasons to change (validation, DB, email, logging)!
// ✅ GOOD: Single responsibility per class
class UserService {
private UserValidator validator;
private UserRepository repository;
private EmailService emailService;
private Logger logger;
void createUser(User user) {
validator.validate(user); // Validation
repository.save(user); // Persistence
emailService.sendWelcome(user); // Email
logger.log("User created"); // Logging
}
}
class UserValidator {
void validate(User user) {
if (user.email == null) throw new IllegalArgumentException("Invalid email");
}
}
class UserRepository {
void save(User user) {
// Database logic only
}
}
// ===========================================
// 2. OPEN/CLOSED PRINCIPLE (OCP)
// ===========================================
// ❌ BAD: Modify existing code for new shapes
class AreaCalculator {
double calculateArea(Object shape) {
if (shape instanceof Circle) {
Circle circle = (Circle) shape;
return Math.PI * circle.radius * circle.radius;
} else if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle) shape;
return rect.width * rect.height;
}
// Add new shape? Modify this method!
return 0;
}
}
// ✅ GOOD: Open for extension, closed for modification
interface Shape {
double area();
}
class Circle implements Shape {
double radius;
public double area() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
double width, height;
public double area() {
return width * height;
}
}
class Triangle implements Shape {
double base, height;
public double area() {
return 0.5 * base * height;
}
}
class AreaCalculator {
double calculateArea(Shape shape) {
return shape.area(); // No modification needed for new shapes!
}
}
// ===========================================
// 3. LISKOV SUBSTITUTION PRINCIPLE (LSP)
// ===========================================
// ❌ BAD: Square violates LSP
class Rectangle {
protected int width, height;
void setWidth(int width) { this.width = width; }
void setHeight(int height) { this.height = height; }
int area() { return width * height; }
}
class Square extends Rectangle {
@Override
void setWidth(int width) {
this.width = width;
this.height = width; // ❌ Unexpected behavior!
}
@Override
void setHeight(int height) {
this.width = height;
this.height = height; // ❌ Violates LSP!
}
}
void testRectangle(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(10);
assert rect.area() == 50; // ❌ Fails for Square!
}
// ✅ GOOD: Separate hierarchies
interface Shape {
int area();
}
class Rectangle implements Shape {
private int width, height;
Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public int area() { return width * height; }
}
class Square implements Shape {
private int side;
Square(int side) { this.side = side; }
public int area() { return side * side; }
}
// ===========================================
// 4. INTERFACE SEGREGATION PRINCIPLE (ISP)
// ===========================================
// ❌ BAD: Fat interface (forces unnecessary implementation)
interface Worker {
void work();
void eat();
void sleep();
}
class HumanWorker implements Worker {
public void work() { /* work */ }
public void eat() { /* eat */ }
public void sleep() { /* sleep */ }
}
class RobotWorker implements Worker {
public void work() { /* work */ }
public void eat() { /* ❌ Robots don't eat! */ }
public void sleep() { /* ❌ Robots don't sleep! */ }
}
// ✅ GOOD: Segregated interfaces
interface Workable {
void work();
}
interface Eatable {
void eat();
}
interface Sleepable {
void sleep();
}
class HumanWorker implements Workable, Eatable, Sleepable {
public void work() { /* work */ }
public void eat() { /* eat */ }
public void sleep() { /* sleep */ }
}
class RobotWorker implements Workable {
public void work() { /* work */ }
// No unnecessary methods!
}
// ===========================================
// 5. DEPENDENCY INVERSION PRINCIPLE (DIP)
// ===========================================
// ❌ BAD: High-level depends on low-level (tight coupling)
class EmailService {
void sendEmail(String message) {
System.out.println("Email: " + message);
}
}
class Notification {
private EmailService emailService = new EmailService(); // ❌ Concrete dependency!
void notify(String message) {
emailService.sendEmail(message);
}
}
// Problem: Can't switch to SMS without modifying Notification!
// ✅ GOOD: Depend on abstraction
interface MessageService {
void send(String message);
}
class EmailService implements MessageService {
public void send(String message) {
System.out.println("Email: " + message);
}
}
class SMSService implements MessageService {
public void send(String message) {
System.out.println("SMS: " + message);
}
}
class Notification {
private MessageService messageService; // ✅ Abstraction!
Notification(MessageService messageService) {
this.messageService = messageService; // Dependency injection
}
void notify(String message) {
messageService.send(message);
}
}
// Usage:
Notification emailNotif = new Notification(new EmailService());
Notification smsNotif = new Notification(new SMSService());5. The Comparison & Decision Layer
| Principle | Without | With |
|---|---|---|
| SRP | God class (hard to maintain) | Focused classes (easy to change) |
| OCP | Modify code for extensions | Add new classes only |
| LSP | Broken substitution | Seamless polymorphism |
| ISP | Fat interfaces (forced methods) | Small interfaces (implement what you need) |
| DIP | Tight coupling | Loose coupling (testable) |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "Explain Liskov Substitution Principle with Square-Rectangle problem." Answer: LSP violated when Square extends Rectangle!
java
// Problem:
Rectangle rect = new Square(5);
rect.setWidth(10);
rect.setHeight(5);
// Expected: area = 50
// Actual: area = 25 (Square changes both dimensions!)
// LSP: Subtype must be substitutable without breaking expectations
// Solution: Don't inherit Square from Rectangle
// Use separate classes or common Shape interfacePro-Tips:
- SRP: One axis of change:
java
// Ask: "What's the reason to change?"
// UserService changes if:
// - Business logic changes ✅ (one reason)
// - Database changes ❌ (extract to Repository)
// - Email changes ❌ (extract to EmailService)- DIP: Layers depend upward:
json
[Presentation Layer]
↓ depends on
[Business Interface] ← abstraction
↑ implements
[Business Implementation]
↓ depends on
[Data Interface] ← abstraction
↑ implements
[Data Implementation]
High-level modules don't depend on low-level modules!
Both depend on abstractions!