Lesson Completion
Back to course

SOLID Principles in Practice: Applying Design Principles

Beginner
12 minutes4.6Java

1. The Hook (The "Byte-Sized" Intro)

  • In a Nutshell: SOLID in practice = refactor toward principles.
  • SRP refactoring: Extract responsibilities into separate classes.
  • OCP refactoring: Replace if-else with polymorphism (Strategy pattern).
  • LSP refactoring: Fix substitutability violations (Square ≠ Rectangle).
  • ISP refactoring: Split fat interfaces into focused ones.
  • DIP refactoring: Inject dependencies via interfaces.
  • Process: Identify smell → apply principle → verify improvement.
  • Key: Refactor incrementally with tests!

Think of home renovation. Refactoring = remodel step-by-step (not demolish). SRP = one room, one purpose (bedroom not kitchen+office). OCP = modular furniture (add pieces without walls). DIP = universal outlets (plug in anything). Tests = safety inspector (verify each change)!


2. Conceptual Clarity (The "Simple" Tier)

💡 The Analogy

  • SRP Refactoring: Swiss Army knife → separate tools
  • OCP Refactoring: Hard-coded menu → plug-in system
  • DIP Refactoring: Direct wiring → power outlets

3. Technical Mastery (The "Deep Dive")

java
// =========================================== // 1. SRP REFACTORING // =========================================== // ❌ BEFORE: God class violating SRP class UserService { void register(User user) { // Validation if (user.getEmail() == null) throw new Exception(); // Save to database Connection conn = getConnection(); // SQL... // Send email EmailClient.send(user.getEmail(), "Welcome"); // Log System.out.println("User registered"); } } // Problem: 4 responsibilities (validation, DB, email, logging) // ✅ AFTER: SRP applied class UserService { private final UserValidator validator; private final UserRepository repository; private final EmailService emailService; private final AuditLogger logger; void register(User user) { validator.validate(user); repository.save(user); emailService.sendWelcome(user); logger.log("User registered: " + user.getId()); } } class UserValidator { void validate(User user) { if (user.getEmail() == null) { throw new IllegalArgumentException("Email required"); } } } // =========================================== // 2. OCP REFACTORING // =========================================== // ❌ BEFORE: Violates OCP (modify for new types) class DiscountCalculator { double calculate(String customerType, double amount) { if ("PREMIUM".equals(customerType)) { return amount * 0.8; // 20% discount } else if ("GOLD".equals(customerType)) { return amount * 0.9; // 10% discount } else if ("SILVER".equals(customerType)) { return amount * 0.95; // 5% discount } return amount; } } // Add new type? Modify this method! // ✅ AFTER: OCP applied (Strategy pattern) interface DiscountStrategy { double apply(double amount); } class PremiumDiscount implements DiscountStrategy { public double apply(double amount) { return amount * 0.8; } } class GoldDiscount implements DiscountStrategy { public double apply(double amount) { return amount * 0.9; } } class DiscountCalculator { double calculate(DiscountStrategy strategy, double amount) { return strategy.apply(amount); } } // Add new discount? Create new class, no modification! // =========================================== // 3. LSP REFACTORING // =========================================== // ❌ BEFORE: LSP violation 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; // ❌ Breaks LSP! } } // Test: void testRectangle(Rectangle r) { r.setWidth(5); r.setHeight(10); assert r.area() == 50; // ❌ Fails for Square! } // ✅ AFTER: LSP applied (composition) interface Shape { int area(); } class Rectangle implements Shape { private final 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 final int side; Square(int side) { this.side = side; } public int area() { return side * side; } } // =========================================== // 4. ISP REFACTORING // =========================================== // ❌ BEFORE: Fat interface interface Worker { void work(); void eat(); void sleep(); } class Robot implements Worker { public void work() { /* work */ } public void eat() { /* ❌ Robots don't eat! */ } public void sleep() { /* ❌ Robots don't sleep! */ } } // ✅ AFTER: ISP applied (segregated interfaces) interface Workable { void work(); } interface Eatable { void eat(); } interface Sleepable { void sleep(); } class Human implements Workable, Eatable, Sleepable { public void work() { /* work */ } public void eat() { /* eat */ } public void sleep() { /* sleep */ } } class Robot implements Workable { public void work() { /* work */ } // No forced methods! } // =========================================== // 5. DIP REFACTORING // =========================================== // ❌ BEFORE: Tight coupling (depends on concrete class) class OrderProcessor { private MySQLDatabase database = new MySQLDatabase(); // ❌ Concrete! void process(Order order) { database.save(order); } } // Can't switch to PostgreSQL without modifying OrderProcessor! // ✅ AFTER: DIP applied (depend on abstraction) interface Database { void save(Order order); } class MySQLDatabase implements Database { public void save(Order order) { // MySQL-specific code } } class PostgreSQLDatabase implements Database { public void save(Order order) { // PostgreSQL-specific code } } class OrderProcessor { private final Database database; // ✅ Abstraction! OrderProcessor(Database database) { // Dependency injection this.database = database; } void process(Order order) { database.save(order); } } // Usage: Easy to switch! Database db = new PostgreSQLDatabase(); OrderProcessor processor = new OrderProcessor(db);

5. The Comparison & Decision Layer

PrincipleSmellRefactoring
SRPGod classExtract classes by responsibility
OCPif-else chainsStrategy/Factory pattern
LSPBroken substitutionUse composition over inheritance
ISPForced methodsSplit interfaces
DIPnew ConcreteClass()Inject via interface

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "How do you refactor toward SOLID?" Answer: Incremental refactoring with tests!

Process:

  1. Write tests for existing behavior
  2. Identify violation (God class, if-else chain, etc.)
  3. Apply principle (extract class, introduce interface, etc.)
  4. Run tests to verify no breakage
  5. Repeat for next violation
java
// Example: SRP refactoring // 1. Test existing UserService @Test void testRegister() { UserService service = new UserService(); service.register(new User("test@example.com")); // Verify behavior } // 2. Extract EmailService (one responsibility at a time) // 3. Run tests (still pass!) // 4. Extract UserValidator // 5. Run tests (still pass!) // ...

Pro-Tip: SOLID Refactoring Workflow:

text
1. RED: Write failing test 2. GREEN: Make it pass (ugly code OK!) 3. REFACTOR: Apply SOLID principles 4. Test still GREEN → safe refactoring! Never refactor without tests!

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01