Lesson Completion
Back to course

SOLID Principles: Foundation of Good Design

Beginner
12 minutes4.9Java

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

PrincipleAcronymCore IdeaViolation Example
Single ResponsibilitySRPOne reason to changeGod class
Open/ClosedOCPExtend, don't modifyif-else chains
Liskov SubstitutionLSPSubtypes substitutableSquare extends Rectangle
Interface SegregationISPSmall, focused interfacesFat interface
Dependency InversionDIPDepend on abstractionsnew 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

PrincipleWithoutWith
SRPGod class (hard to maintain)Focused classes (easy to change)
OCPModify code for extensionsAdd new classes only
LSPBroken substitutionSeamless polymorphism
ISPFat interfaces (forced methods)Small interfaces (implement what you need)
DIPTight couplingLoose 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 interface

Pro-Tips:

  1. 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)
  1. 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!

Topics Covered

Java Fundamentals

Tags

#java#programming#beginner-friendly

Last Updated

2025-02-01