Module A1 — SOLID + OOP + UML

Complete reference notes · Track A: LLD · Week 3

5 SOLID Principles 4 OOP Pillars 2 UML Diagrams 1 Mini Project
⚡ Interactive Visual Version ← Recommended for learning. This page is the printable reference.

🎯 Module Overview

Duration: 1 Week     Track: A — Low-Level Design (LLD)     Prerequisites: Phase 0 complete

Goal: Build the design instinct that underpins every one of the 23 patterns you’ll learn in Weeks 4–7. SOLID is not a checklist — it’s a way of thinking about change.

Learning Objectives

By the end of Module A1, you will:


OOP Foundations — The 4 Pillars

🔒 Pillar 1 — Encapsulation

What it is: Bundle data + behaviour together. Hide internal state. Expose only what is necessary.

Bad (no encapsulation):

class BankAccount {
    public double balance;  // Anyone can set this to anything!
}
account.balance = -99999;  // No validation possible

Good (encapsulated):

class BankAccount {
    private double balance;

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        this.balance += amount;
    }

    public double getBalance() { return balance; }
}

Why it matters in SD: Encapsulation creates stable interfaces. When internals change (e.g., switching from double to BigDecimal for precision), callers don’t break.


🎭 Pillar 2 — Abstraction

What it is: Expose WHAT a thing does, hide HOW it does it. Program to interfaces, not implementations.

interface PaymentProcessor {
    boolean process(Payment payment);
}

class StripeProcessor implements PaymentProcessor {
    public boolean process(Payment payment) { /* Stripe API logic */ }
}

class PayPalProcessor implements PaymentProcessor {
    public boolean process(Payment payment) { /* PayPal API logic */ }
}

// Caller doesn't care which processor — just calls process()
PaymentProcessor processor = getProcessor(user.preference);
processor.process(payment);

Interview Tip: “Programming to an interface” is the foundation of the Strategy, Bridge, and Factory patterns.


🧬 Pillar 3 — Inheritance

What it is: Child class inherits state and behaviour from parent.

Use inheritance for: IS-A relationships (Dog IS-A Animal, SavingsAccount IS-A Account) Avoid inheritance for: HAS-A relationships (Car HAS-A Engine — use composition instead)

// IS-A: correct use
abstract class Vehicle {
    protected String licensePlate;
    abstract int getMaxSpeed();
}
class Car extends Vehicle {
    public int getMaxSpeed() { return 180; }
}

// HAS-A: use composition, NOT inheritance
class Car {
    private Engine engine;   // Composition
    private GPS gps;
}

Critical rule: Favour Composition Over Inheritance. When the parent changes, all children may break.


🔀 Pillar 4 — Polymorphism

What it is: Same interface, different behaviour. One method call, many possible implementations.

// Runtime polymorphism
Shape[] shapes = { new Circle(5), new Rectangle(4, 6) };
for (Shape shape : shapes) {
    System.out.println(shape.area());  // Each calls its own area()
}

// Compile-time polymorphism (overloading)
class Logger {
    void log(String message) { ... }
    void log(String message, Level level) { ... }
    void log(Exception e) { ... }
}

The SOLID Principles

S — Single Responsibility Principle (SRP)

“A class should have only one reason to change.”

Violation:

class Invoice {
    public double calculateTotal() { ... }    // Reason 1: business logic
    public void saveToDatabase() { ... }      // Reason 2: DB schema
    public void printInvoice() { ... }        // Reason 3: report format
    public void sendEmail() { ... }           // Reason 4: email provider
}

Fix:

class Invoice          { public double calculateTotal() { ... } }
class InvoiceRepository { public void save(Invoice inv) { ... } }
class InvoicePrinter   { public void print(Invoice inv) { ... } }
class InvoiceEmailer   { public void send(Invoice inv, String email) { ... } }

Interview Tip: SRP violations are the #1 reason codebases become unmaintainable. Count “reasons to change” — should always be 1.


O — Open/Closed Principle (OCP)

“Open for extension, but closed for modification.”

Violation (if/else on type):

class DiscountCalculator {
    public double calculate(String customerType, double price) {
        if (customerType.equals("REGULAR")) return price * 0.95;
        if (customerType.equals("PREMIUM")) return price * 0.85;
        // Every new type requires modifying this class!
        return price;
    }
}

Fix (Strategy Pattern):

interface DiscountStrategy { double apply(double price); }
class RegularDiscount implements DiscountStrategy { public double apply(double p) { return p * 0.95; } }
class PremiumDiscount implements DiscountStrategy { public double apply(double p) { return p * 0.85; } }
// Add VIP: create new class, touch NOTHING else
class VIPDiscount implements DiscountStrategy { public double apply(double p) { return p * 0.60; } }

class DiscountCalculator {
    public double calculate(DiscountStrategy strategy, double price) {
        return strategy.apply(price);
    }
}

Interview Tip: OCP is directly embodied by the Strategy Pattern (A2) and Template Method (A4).


L — Liskov Substitution Principle (LSP)

“Subtypes must be substitutable for their base types without altering program correctness.”

Violation (Rectangle/Square trap):

class Square extends Rectangle {
    public void setWidth(int w)  { this.width = w; this.height = w; }
    public void setHeight(int h) { this.width = h; this.height = h; }
}
// testArea(r): r.setWidth(5); r.setHeight(4); assert r.area() == 20;
// FAILS for Square (gives 16)!

Fix:

interface Shape { int area(); }
class Rectangle implements Shape { ... }
class Square    implements Shape { ... }
// Both honour the Shape contract independently

Interview Tip: LSP violations appear as instanceof checks or UnsupportedOperationException. Classic trap: Penguin extends Bird (fly() throws!).


I — Interface Segregation Principle (ISP)

“Clients should not be forced to depend on interfaces they do not use.”

Violation:

interface Worker {
    void work();
    void eat();    // robots don't eat
    void sleep();  // robots don't sleep
}
class RobotWorker implements Worker {
    public void work() { ... }
    public void eat()  { throw new UnsupportedOperationException(); }
    public void sleep(){ throw new UnsupportedOperationException(); }
}

Fix:

interface Workable { void work(); }
interface Feedable  { void eat(); }
interface Restable  { void sleep(); }

class HumanWorker implements Workable, Feedable, Restable { ... }
class RobotWorker implements Workable { ... }  // Only what it needs

D — Dependency Inversion Principle (DIP)

“High-level modules should not depend on low-level modules. Both should depend on abstractions.”

Violation:

class OrderService {
    private MySQLDatabase database;  // Concrete!
    private EmailService emailer;    // Concrete!

    public OrderService() {
        this.database = new MySQLDatabase();  // Hard-coded
        this.emailer = new EmailService();
    }
}

Fix (Constructor Injection):

interface OrderRepository    { void save(Order order); }
interface NotificationService { void notify(Order order); }

class OrderService {
    private final OrderRepository    repository;
    private final NotificationService notifier;

    public OrderService(OrderRepository repository, NotificationService notifier) {
        this.repository = repository;
        this.notifier = notifier;
    }
}

// Wiring:
new OrderService(new PostgreSQLRepository(), new SMSNotificationService());

Interview Tip: DIP is the principle that makes unit testing possible. Without it, you can’t mock dependencies.


SOLID Violations Quick-Reference

Code SmellPrinciple ViolatedFix
Class with 5+ unrelated methodsSRPSplit into focused classes
if/else or switch on typeOCPStrategy pattern
Subclass throws UnsupportedOperationExceptionLSPRedesign hierarchy or use composition
instanceof checks in polymorphic codeLSPFix inheritance, use polymorphism properly
Fat interface with 10+ methodsISPSplit into role-specific interfaces
new ConcreteClass() inside serviceDIPInject via constructor / DI container
Hard to unit test (can't mock)DIPIntroduce interface, inject dependency

UML Reference

Class Diagram Relationships

Association (uses):          A ────────── B
Aggregation (has-a, weak):   A ◇────────── B   (B can exist without A)
Composition (has-a, strong): A ◆────────── B   (B cannot exist without A)
Inheritance (is-a):          A ────────▷ B
Interface Implementation:    A ─ ─ ─ ─ ▷ B    (dashed)
Dependency (transient):      A ─ ─ ─ ─ ─> B
Multiplicities: 1 0..1 * 1..* 2..5

Sequence Diagram Notation

Customer     ParkingLot    Floor      Spot
   |              |          |          |
   |──park(car)──>|          |          |
   |              |──findFloor()──>|    |
   |              |          |──findSpot()──>|
   |              |          |          |──reserve()
   |              |          |<──spot──-|
   |              |<──floor──|          |
   |<──ticket─────|          |          |

UML Interview Tips:


📝 Interview Tips Summary

PrincipleOne-liner to say in interviewPattern it enables
SRP"Each class has one reason to change"— (foundational)
OCP"Open for extension, closed for modification"Strategy, Template Method
LSP"Subtypes must honour the parent's contract"— (prevents bad inheritance)
ISP"Many focused interfaces > one fat interface"— (enables clean composition)
DIP"Depend on abstractions, not concretions"Factory, DI containers

✅ Module A1 Completion Checklist

→ When complete: Ready for Module A2 — Creational Design Patterns

⚡ Open Interactive Version ↑ Back to Roadmap