Track A · LLD · Module A3 · Week 5

Structural
Patterns

Adapter · Decorator · Proxy · Composite
Facade · Bridge · Flyweight
Week5 of 24
Patterns7
Systems7 real
ProjectSplitwise
PrereqA1 + A2
Overview
Pattern Deep Dives
Key Distinctions
Splitwise Project
Tasks
Checklist

Structural patterns deal with object composition — how classes and objects are assembled into larger structures. They ensure that when one part changes, the entire structure doesn't need to be redesigned.

01
Adapter
→ Vending Machine
Make incompatible interfaces work together.
02
Decorator
→ Pizza Billing
Add responsibilities dynamically. Avoids subclass explosion.
03
Proxy
→ Car Rental
Control access to an object. Same interface, intercepted calls.
04
Composite
→ File System
Treat leaf and branch uniformly through one interface.
05
Facade
→ Splitwise
Simplified interface hiding a complex subsystem.
06
Bridge
→ CricBuzz
Decouple two dimensions of variation. Replaces subclass explosion.
07
Flyweight
→ TrueCaller
Share intrinsic state across millions of objects. Memory efficiency.
PATTERNTRIGGER / SMELLKEY MECHANISMREAL WORLD
AdapterIncompatible interface from third-party/legacyWrapper translates interface A → interface BPayment gateways, legacy DB drivers
DecoratorN features × M objects = too many subclassesIS-A + HAS-A same type; wraps and delegatesJava I/O streams, HTTP middleware
ProxyNeed auth/caching/logging without touching real objectSame interface, intercepts before delegatingSpring AOP, Hibernate lazy loading, CDN
CompositeTree structure; want uniform treatment of leaf/branchComponent interface; Composite holds childrenHTML DOM, UI widget trees, file system
FacadeClient must coordinate many subsystem classesHigh-level class orchestrates subsystemsSpring ApplicationContext, SDK clients
BridgeTwo orthogonal dimensions exploding into N×M classesAbstraction holds Implementation reference (bridge)Notification type × channel, JDBC drivers
FlyweightMillions of objects exhausting memoryShared intrinsic state in factory pool; extrinsic passed inJava String pool, game particles, TrueCaller
01Adapter
02Decorator
03Proxy
04Composite
05Facade
06Bridge
07Flyweight
01
Adapter
REAL SYSTEM → Vending Machine Integration
Convert the interface of a class into another interface clients expect. Lets classes work together that couldn't otherwise because of incompatible interfaces.
VendingMachineAdapter.javaJAVA
// Your system expects this interface
interface VendingMachine {
    void insertCoin(int amount);      // amount in paise
    void selectProduct(String m3-code); // e.g. "A1", "B2"
    void dispense();
    int  getChange();
}

// Third-party machine — incompatible interface
class NewVendorMachine {
    public void payAmount(double rupees) { /* ... */ }
    public void chooseItem(int itemId)   { /* ... */ }
    public void releaseItem()             { /* ... */ }
    public double calculateChange()       { return 5.50; }
}

// ADAPTER — wraps new vendor machine, speaks old interface
class VendingMachineAdapter implements VendingMachine {
    private final NewVendorMachine adaptee;

    public VendingMachineAdapter(NewVendorMachine m) { this.adaptee = m; }

    @Override
    public void insertCoin(int amount) {
        adaptee.payAmount(amount / 100.0); // paise → rupees
    }
    @Override
    public void selectProduct(String m3-code) {
        int id = codeToId.get(m3-code);         // "A1" → 1
        adaptee.chooseItem(id);
    }
    @Override
    public void  dispense()   { adaptee.releaseItem(); }
    @Override
    public int   getChange()  { return (int)(adaptee.calculateChange() * 100); }
}

// Client m3-code unchanged — still speaks VendingMachine
VendingMachine vm = new VendingMachineAdapter(new NewVendorMachine());
vm.insertCoin(1000); vm.selectProduct("A1"); vm.dispense();
Interview: "I use Adapter when integrating third-party systems — create an internal interface, write an Adapter per provider. Stripe, PayPal, Razorpay all become swappable behind one PaymentGateway interface."
02
Decorator
REAL SYSTEM → Pizza Billing System
Attach additional responsibilities dynamically. IS-A AND HAS-A the same type simultaneously. Infinitely composable runtime combinations — no subclass explosion.
PizzaDecorator.javaJAVA
interface Pizza { String getDescription(); double getCost(); }

class MargheritaPizza implements Pizza {
    public String getDescription() { return "Margherita"; }
    public double getCost()        { return 200.0; }
}

// ABSTRACT DECORATOR — IS-A Pizza AND HAS-A Pizza
abstract class ToppingDecorator implements Pizza {
    protected final Pizza pizza;
    public ToppingDecorator(Pizza p) { this.pizza = p; }
}

// Concrete decorators — each adds exactly one topping
class CheeseDecorator extends ToppingDecorator {
    public CheeseDecorator(Pizza p) { super(p); }
    public String getDescription() { return pizza.getDescription() + " + Cheese"; }
    public double getCost()        { return pizza.getCost() + 50.0; }
}
class MushroomDecorator extends ToppingDecorator {
    public String getDescription() { return pizza.getDescription() + " + Mushroom"; }
    public double getCost()        { return pizza.getCost() + 35.0; }
    public MushroomDecorator(Pizza p) { super(p); }
}

// Runtime composition — any order, any combination
Pizza order = new CheeseDecorator(
                 new MushroomDecorator(
                    new CheeseDecorator(   // double cheese!
                       new MargheritaPizza())));
// → "Margherita + Cheese + Mushroom + Cheese"  cost: 335.0
Interview: "Java I/O is Decorator: BufferedReader(InputStreamReader(FileInputStream)). Each wrapper adds one responsibility. Real production use: MetricService(CachingService(DatabaseService)) — cross-cutting concerns as transparent wrappers."
03
Proxy
REAL SYSTEM → Car Rental (Protection Proxy)
Provide a surrogate to control access. Same interface as real subject — client can't tell the difference. Purpose: intercept calls for auth, caching, logging, or lazy init.
CarRentalProxy.java — Protection ProxyJAVA
interface CarRentalService {
    Car rentCar(String model, User user);
    void returnCar(String carId, User user);
}

class CarRentalProxy implements CarRentalService {
    private final CarRentalServiceImpl real;
    private final AuthService auth;
    private final Logger log;

    @Override
    public Car rentCar(String model, User user) {
        // 1. Authorization (Protection Proxy)
        if (!auth.hasValidLicense(user))
            throw new UnauthorizedException("No valid license");

        // 2. Pre-logging
        log.log("Renting " + model + " for user " + user.getId());

        // 3. Delegate to real service
        Car car = real.rentCar(model, user);

        // 4. Post-logging
        log.log("Assigned car " + car.getId());
        return car;
    }
    // returnCar similarly delegates after logging
}

// Client sees same interface — proxy is completely transparent
CarRentalService svc = new CarRentalProxy(real, auth, log);
Car c = svc.rentCar("Camry", currentUser);

Three Proxy Types

  • Virtual: Lazy initialisation — defer expensive creation
  • Protection: Auth/permissions check before delegating
  • Remote: Represents object in different process (gRPC stub)

Real-World Proxies

  • Spring @Transactional, @Cacheable → runtime proxy
  • Hibernate lazy loading → Virtual proxy
  • gRPC generated stubs → Remote proxy
  • CDN → Remote proxy for assets
Interview: "Spring AOP generates proxies at runtime — @Cacheable wraps method, checks cache first. @Transactional wraps method in DB transaction. Understanding this means understanding how Spring works internally."
04
Composite
REAL SYSTEM → File System (File + Directory)
Compose objects into tree structures. Client treats leaf (File) and composite (Directory) identically — one interface, recursive operations, no instanceof checks needed.
FileSystemComponent.javaJAVA
// Uniform component interface — same for File AND Directory
interface FileSystemComponent {
    String getName();
    long   getSize();
    void   display(String indent);
    void   delete();
}

// LEAF — no children
class File implements FileSystemComponent {
    private final String name; private final long size;
    public long   getSize()           { return size; }
    public void   display(String ind) { System.out.println(ind + "📄 " + name); }
    public void   delete()            { System.out.println("Delete file: " + name); }
    public String getName()           { return name; }
    public File(String n, long s)   { name=n; size=s; }
}

// COMPOSITE — holds children, operations recurse
class Directory implements FileSystemComponent {
    private final String name;
    private final List<FileSystemComponent> children = new ArrayList<>();

    public void add(FileSystemComponent c) { children.add(c); }

    // Recursive — works for any depth of nesting
    public long getSize() {
        return children.stream().mapToLong(FileSystemComponent::getSize).sum();
    }
    public void display(String ind) {
        System.out.println(ind + "📁 " + name + " (" + getSize() + " B)");
        children.forEach(c -> c.display(ind + "  "));
    }
    public void delete() { children.forEach(FileSystemComponent::delete); }
    public String getName() { return name; }
    public Directory(String n) { name = n; }
}

// Client — no instanceof, no type checks needed
Directory root = new Directory("root");
root.add(new File("README.md", 256));
root.add(src);          // src is a Directory — same add() call
root.getSize();         // Recursively sums all nested files
Interview: "Composite is the pattern behind the HTML DOM — a div can contain buttons or other divs, all support the same operations. When you see a tree where leaf and branch must be interchangeable, Composite is the answer."
05
Facade
REAL SYSTEM → Splitwise Expense Management
Provide a unified, simplified interface to a complex subsystem. Subsystem classes remain usable directly but Facade provides the common-path shortcut. Client only needs to know Facade.
SplitwiseFacade.javaJAVA
// Complex subsystems — many classes, many responsibilities
class ExpenseService   { Expense createExpense(...) {...} }
class SplitCalculator { Map calculateEqualSplit(...) {...} }
class BalanceService  { Map getNetBalances(...) {...} }
class NotificationService { void notifyMembers(...) {...} }

// FACADE — one class, simple operations, hides all complexity
class SplitwiseFacade {
    private final ExpenseService      expenses;
    private final SplitCalculator    calculator;
    private final BalanceService     balances;
    private final NotificationService notifier;

    // High-level operation — orchestrates 4 subsystems
    public void addExpenseEqualSplit(String groupId, String desc,
                                      double amount, String paidBy,
                                      List<String> members) {
        Expense expense = expenses.createExpense(desc, amount, paidBy);
        Map splits      = calculator.calculateEqualSplit(expense, members);
        balances.updateBalances(groupId, splits, paidBy);
        notifier.notifyExpenseAdded(members, expense);
    }

    public List<Transaction> getSimplifiedSettlements(String groupId) {
        Map<String, Double> netBalances = balances.getNetBalances(groupId);
        return SimplifyAlgorithm.simplify(netBalances); // min transactions
    }
}

// Client — one method call does what used to take 10
SplitwiseFacade sw = new SplitwiseFacade();
sw.addExpenseEqualSplit("grp1", "Dinner", 1200.0, "ajay", members);
Interview: "Facade vs Adapter: Facade simplifies access to a SUBSYSTEM (multiple classes). Adapter converts ONE incompatible interface. Facade vs Mediator: Facade is one-directional — client talks to Facade. Mediator encapsulates how PEERS communicate with each other."
06
Bridge
REAL SYSTEM → CricBuzz Notification System
Decouple abstraction from implementation so both can vary independently. Replaces N×M subclass explosion with N+M classes. The "bridge" is a reference held in the abstraction.
CricBuzzBridge.java — Notification Type × ChannelJAVA
// IMPLEMENTATION — HOW to send (one dimension)
interface NotificationSender { void send(String to, String msg); }
class SMSSender   implements NotificationSender { /* ... */ }
class EmailSender implements NotificationSender { /* ... */ }
class PushSender  implements NotificationSender { /* ... */ }

// ABSTRACTION — WHAT to send (other dimension)
abstract class CricketNotification {
    protected final NotificationSender sender; // THE BRIDGE
    public CricketNotification(NotificationSender s) { this.sender = s; }
    public abstract void notify(String recipient, Object event);
}

// Refined abstractions — each is a notification type
class WicketNotification extends CricketNotification {
    public WicketNotification(NotificationSender s) { super(s); }
    public void notify(String r, Object e) {
        sender.send(r, "WICKET! " + e + " is out!"); // uses bridge
    }
}
class SixNotification extends CricketNotification {
    public SixNotification(NotificationSender s) { super(s); }
    public void notify(String r, Object e) {
        sender.send(r, "SIX! " + e + " smashes it!");
    }
}

// Mix and match — N types × M channels without N×M classes
new WicketNotification(new SMSSender()).notify("user1", "Kohli");
new WicketNotification(new EmailSender()).notify("fan@email", "Rohit");
new SixNotification(new PushSender()).notify("device_xyz", "Dhoni");
Interview: "The tell-tale sign for Bridge: two independent dimensions of variation (what × how, shape × rendering, device × OS). Without Bridge: N×M subclasses. With Bridge: N+M classes connected by composition."
07
Flyweight
REAL SYSTEM → TrueCaller (1 billion contacts)
Share intrinsic state across huge numbers of objects. Extrinsic state is passed in at runtime. Factory ensures pool reuse. Result: 10,000× memory reduction possible.
TrueCallerFlyweight.javaJAVA
// FLYWEIGHT — stores INTRINSIC state (shared, immutable)
class ContactMetadata {
    private final String operatorName;  // "Jio", "Airtel" — thousands share this
    private final String contactType;   // "SPAM", "BUSINESS" — few unique values
    private final String spamLabel;     // "Telemarketer", null — shared
    // All final — immutable, so safely shared across threads
}

// FLYWEIGHT FACTORY — pool ensures reuse
class ContactMetadataFactory {
    private static final Map<String,ContactMetadata> pool = new HashMap<>();

    public static ContactMetadata get(String op, String type, String spam) {
        String key = op + "|" + type + "|" + spam;
        return pool.computeIfAbsent(key, k -> new ContactMetadata(op, type, spam));
    }
}

// CLIENT CONTEXT — stores EXTRINSIC state (unique per contact)
class PhoneContact {
    private final String phoneNumber;  // unique — extrinsic
    private final String callerName;   // unique — extrinsic
    private final ContactMetadata meta; // SHARED — flyweight

    public PhoneContact(String num, String name, String op, String type, String spam) {
        this.phoneNumber = num;
        this.callerName  = name;
        this.meta = ContactMetadataFactory.get(op, type, spam); // pool lookup
    }
}

// Memory impact (1 billion contacts):
// Without Flyweight: 1B × 200B metadata = 200 GB
// With Flyweight:    ~1000 unique combos × 200B = 200 KB shared
//                   + 1B × ~30B (phone + name only) = 30 GB extrinsic
Interview: "Java's String pool IS Flyweight — string literals are interned and shared. Integer.valueOf(-128 to 127) returns cached instances. Game engines use Flyweight for particles: one texture/physics object shared by 100,000 bullets."
THE MOST COMMONLY CONFUSED TRIO: ADAPTER vs DECORATOR vs PROXY
Dimension
Adapter
Decorator
Purpose
Convert incompatible interface
Add behaviour dynamically
Interface
Changes the interface (A→B)
Same interface as component
Wrapped type
Adaptee (different type)
Same component type (IS-A + HAS-A)
When to use
Third-party/legacy integration
Adding features without subclassing
ADAPTER vs DECORATOR vs PROXY (continued)
Dimension
Proxy
Facade
Purpose
Control access to one object
Simplify interface to a subsystem
Interface
Same as real subject
New simplified interface
Scope
One object
Entire subsystem (many objects)
When to use
Auth, caching, logging, lazy init
Client shouldn't know subsystem details
// THE ONE-LINE TEST
Adapter: "I need to use this component but its interface is wrong"
Decorator: "I need to add features to this object without modifying its class"
Proxy: "I need to control who/how accesses this object"
Composite: "I have a tree and want leaf + branch to behave the same"
Facade: "I want to hide this complex subsystem behind one simple class"
Bridge: "I have two dimensions of variation and don't want N×M subclasses"
Flyweight: "I have millions of similar objects and I'm running out of memory"
PATTERNADVANTAGETRADE-OFF
AdapterIntegration without touching existing m3-codeExtra indirection; translation bugs possible
DecoratorInfinite runtime combinations, no subclass explosionDeep chains hard to debug; decoration order matters
ProxyTransparent cross-cutting concernsExtra indirection; proxy-related bugs subtle
CompositeUniform tree operations, no instanceofHard to restrict component types in tree
FacadeSimplifies client m3-code dramaticallyCan become a god object if over-loaded
BridgeTwo dimensions vary independentlyUp-front complexity; must identify dimensions correctly
FlyweightMassive memory savingsClient must manage extrinsic state; no object identity
MINI PROJECT
Splitwise Clone

Complete LLD implementation with the Simplify Algorithm. This is the most architecturally rich problem in Module A3 — it naturally requires Facade + Algorithm + Adapter + Decorator + Composite.

The Splitwise Simplify Algorithm — Minimum Transactions

Problem: Given a group's net balances (positive = owed money, negative = owes money), find the minimum number of transactions to settle all debts.

  • 1
    Calculate net balance per person: sum what they paid, minus what they owe across all expenses.
  • 2
    Separate into creditors (positive balance — owed money) and debtors (negative balance — owe money).
  • 3
    Use two priority queues: max-heap of creditors, min-heap of debtors.
  • 4
    Greedy loop: take largest creditor + largest debtor. Settle min(credit, debt). If credit > debt, creditor still has balance → re-insert remainder.
  • 5
    Each loop iteration = one transaction. Loop ends when all queues empty = all debts settled.
Example: Ajay:+600, Ram:-400, Priya:-200, Rahul:+100, Sita:-100
Naive: up to 4 transactions. Algorithm: 3 minimum
→ Ram pays Ajay 400 | Priya pays Ajay 200 | Sita pays Rahul 100
PATTERN USAGE IN SPLITWISE
COMPONENTPATTERNWHY THIS PATTERN
SplitwiseFacadeFacadeUnified API hiding UserService, ExpenseService, BalanceService, Notifier
EqualSplit / PctSplit / ExactSplitStrategy (preview A4)Interchangeable algorithms for splitting an expense
TaxDecorator, ServiceChargeDecoratorDecoratorAdd charges to base expense dynamically at runtime
WhatsAppAdapter, EmailAdapterAdapterNormalize incompatible third-party notification APIs
User + Group (for notifications)CompositeNotify individual or entire group with same call
01
Pattern Recognition — 6 Scenarios
~1.5 hrs

Identify the correct Structural pattern. One sentence justification each.

1. Payment lib accepts PaymentRequest, your system has Order objects.
2. Security system logs all DB access attempts without modifying DB class.
3. Panel can contain Button, Label, or another Panel. All support render().
4. HomeController.leaveHome() controls Lights, Security, Climate at once.
5. 10,000 bullets/sec — each bullet has unique position, shared appearance.
6. Notification type (Alert/Reminder) independent of channel (SMS/Email/Push).
02
Logger Decorator Chain
~2 hrs · m3-code

Implement Logger decorators that compose in any order.

Base: ConsoleLogger — prints to stdout
Decorators:
  TimestampDecorator — prepends "[2024-01-15 14:23:05]"
  LevelDecorator     — prepends [INFO] / [WARN] / [ERROR]
  FileDecorator      — ALSO writes to a log file

Test all 4 combinations:
  new TimestampDecorator(new LevelDecorator(new ConsoleLogger()))
  new LevelDecorator(new TimestampDecorator(new ConsoleLogger()))
  new FileDecorator(new TimestampDecorator(new ConsoleLogger()))
  new FileDecorator(new LevelDecorator(new TimestampDecorator(new ConsoleLogger())))

Show output for each — confirm composition is correct.
03
CachingProxy for WeatherService
~2 hrs · m3-code

Implement a Virtual+Caching Proxy for an expensive weather API.

interface WeatherService {
  WeatherData getWeather(String city);  // expensive HTTP call
}

class RealWeatherService implements WeatherService {
  // Simulate 500ms HTTP call
}

class WeatherServiceProxy implements WeatherService {
  // Cache: Map<city, CacheEntry(data, timestamp)>
  // TTL: 5 minutes
  // Hit: return cache, log "cache hit"
  // Miss: call real service, store, log "cache miss"
}

Test: Call getWeather("Mumbai") 3 times within 5 min
      → only 1 real HTTP call, 2 cache hits
      Wait 5 min, call again → cache miss, new HTTP call
Mini Project — Splitwise with Simplify Algorithm
~5 hrs · full LLD

Complete LLD implementation of Splitwise clone.

Implement:
1. SplitwiseFacade with all 4 subsystems
2. EqualSplit, PercentageSplit, ExactSplit strategies
3. SimplifyAlgorithm (greedy, priority queue approach)
4. TaxDecorator and ServiceChargeDecorator for Expense
5. NotificationAdapter for at least 2 channels
6. UML class diagram showing all patterns

Demo scenario:
  - 5 members: Ajay, Ram, Priya, Rahul, Sita
  - Add 6 expenses (mix of split types)
  - Print net balances
  - Print simplified settlement plan
  - Settle one transaction, reprint balances
0 / 11 completedModule A3 → Structural Patterns
Can implement all 7 Structural patterns from memory
Can distinguish Adapter / Decorator / Proxy in 30 seconds using the one-line test
Understand Composite's part-whole uniformity — no instanceof needed
Know Facade vs Mediator vs Adapter (three "simplify/translate" patterns)
Can identify Bridge's two independent dimensions and the N×M explosion it prevents
Can separate intrinsic from extrinsic state for Flyweight and calculate memory savings
Know which real-world frameworks use each pattern (Spring AOP, Java I/O, String pool)
✏️ Task 1: 6 pattern recognition scenarios answered correctly
✏️ Task 2: Logger Decorator chain — all 4 compositions tested
✏️ Task 3: WeatherService CachingProxy with TTL implemented
✏️ Mini Project: Splitwise clone with Simplify Algorithm + UML complete
NEXT MODULE
A4 — Behavioral Design Patterns
12 patterns: Strategy, Observer, Chain of Responsibility, State, Command, Iterator, Mediator, Memento, Template Method, Visitor, Null Object, Interpreter. Mini Project: BookMyShow with concurrency handling.