CMD Guide
HomeOO & Low-Level Design

Structural

Step 5 in the OO & Low-Level Design path · 8 concepts · 0 problems

0 / 8 complete

📘 Learn Structural from zero

First principle: Structural patterns are about composition over inheritance — assembling objects and classes into larger structures while keeping them flexible and loosely coupled. Instead of building one giant rigid class, you snap together small pieces, each doing one job, connected by shared interfaces.

Analogy — a travel power adapter. Your laptop charger has European prongs; the US wall socket has a different shape. You can't reshape the wall and you can't reshape the charger, so you slot in a small travel adapter: one side fits the wall, the other fits your plug, and inside it just passes the electricity through. That is the Adapter pattern exactly — a thin translator between an interface the client expects and one an existing object provides.

Worked example. Your code logs through a clean interface: interface Logger { void log(String msg); }. You pull in a powerful third-party library whose only method is void writeEntry(int level, String text) — a different shape you can't edit. Write an object adapter that holds the library and delegates:

Now all your code keeps calling logger.log("hi"), unaware of writeEntry. The adapter absorbs the mismatch; swap libraries later → write a new adapter, change nothing else.

Key insight: every structural pattern is a deliberate indirection — you insert a small object between client and target so the two can evolve independently. The only question is why you indirect: to convert an interface (Adapter), to split two dimensions (Bridge), to unify a tree (Composite), to add behavior (Decorator), to simplify a subsystem (Facade), to share memory (Flyweight), or to control access (Proxy).

✨ Added by the guide to build intuition — not from the source course.

🎯 Guided practice

  1. Easy — Decorator: coffee pricing. Base Espresso costs $2. Customers add Milk (+$0.50) and Sugar (+$0.20), in any combination, possibly twice. Don't build EspressoWithMilkAndSugar subclasses.

    Reasoning: (1) Spot the trigger — responsibilities added at runtime in arbitrary combinations → Decorator. (2) Define the shared interface: interface Beverage { double cost(); String desc(); }. (3) Espresso implements Beverage returns 2.0 (the concrete component). (4) Each decorator holds a Beverage and is a Beverage: class Milk implements Beverage { Beverage inner; double cost(){ return inner.cost() + 0.50; } }. It delegates to inner, then adds its bit — this delegate-then-extend is the heart of the pattern. (5) Compose by wrapping: new Sugar(new Milk(new Espresso()))cost() = ((2.0)+0.50)+0.20 = 2.70. (6) Adding caramel = one new class, zero edits to existing ones (open/closed). Note it stays the same Beverage interface throughout — that's what distinguishes Decorator from Adapter, which converts to a different interface.

  2. Medium — Composite: filesystem size. Model files and folders so that totalSize() works identically whether called on a file or a deeply nested folder.

    Reasoning: (1) Trigger — a part-whole tree where the client should treat leaf and group uniformly → Composite. (2) One component interface so the client never branches on type: interface Node { int totalSize(); }. (3) Leaf = class File implements Node { int size; int totalSize(){ return size; } } — base case, returns its own bytes. (4) Composite = class Folder implements Node { List<Node> children; int totalSize(){ int s=0; for(Node c: children) s += c.totalSize(); return s; } } — recursive case, sums children via the same method. (5) Trace /root = Folder[ a.txt(100), Folder sub[ b.txt(50), c.txt(30) ] ]: root.totalSize() → 100 + sub.totalSize() → 100 + (50+30) = 180. (6) The client just calls node.totalSize(); recursion and tree shape are hidden inside Folder — uniform treatment is the payoff. (7) Design decision to name in an interview — GoF's transparency vs safety tradeoff for child operations like add(child): declare them only on Folder (type-safe, but the client may need casts and loses uniformity) or put them on Node and have File throw UnsupportedOperationException (transparent/uniform, but unsafe at runtime). Naming this tradeoff by its canonical terms signals FAANG-level depth.

✨ Added by the guide — work these before the full problem set.

Lessons in this topic

🧠 Review & recall

Active recall is what moves a topic into long-term memory. Flip each card before revealing, then test yourself — your results are saved on this device.

Flashcard
You need a clean Logger interface but a third-party library only exposes writeEntry(int level, String text) that you cannot edit. Which structural pattern fits, and what are its four roles?
tap to reveal →
Adapter Pattern. It makes two incompatible interfaces work together via four roles: Client (uses the service through the Target), Target Interface (the interface the client expects), Adapter (implements Target and holds a reference to the Adaptee, translating calls), and Adaptee (the existing/legacy class with the mismatched interface).
💡 Travel power adapter / translator between two diplomats — convert the interface, don't change either side.
Flashcard
When extending a Vehicle class across two dimensions (type: Car/Truck and transmission: Manual/Automatic) causes a class explosion like ManualCar, AutomaticTruck, etc., which pattern fixes it and how?
tap to reveal →
Bridge Pattern. It separates an abstraction (Vehicle hierarchy, the high-level control logic) from its implementation (Transmission hierarchy, the low-level functional logic) and links them by composition instead of inheritance, so each dimension varies independently and the class count grows additively rather than exponentially.
💡 Universal remote (abstraction) drives many devices (implementations) — split two dimensions, bridge them with a reference.
Flashcard
In the Decorator Pattern, how does a concrete decorator compute its result, and what distinguishes it from the Adapter Pattern?
tap to reveal →
Each concrete decorator holds a reference to a Component (it both wraps one and is one), delegates to that inner component, then adds its own bit. e.g. CheeseDecorator.getCost() returns pizza.getCost() + 2.5. Unlike Adapter, Decorator keeps the SAME interface throughout (it adds behavior) rather than converting to a different interface.
💡 Wrap-then-extend, same interface. new PepperoniDecorator(new CheeseDecorator(new PlainPizza())).
Flashcard
What is the core principle of the Flyweight Pattern, and how does it split an object's state?
tap to reveal →
Flyweight reduces memory by sharing common data across many similar objects. It separates intrinsic state (shared, immutable, stored in the flyweight — e.g. a particle's texture/shape/color) from extrinsic state (unique per object, supplied by the client at call time — e.g. position, velocity, lifespan). A Flyweight Factory caches and returns existing flyweights instead of duplicating them.
💡 Text editor: share font/character (intrinsic), pass position (extrinsic). Factory cache = reuse, don't recreate.
Flashcard
A Facade and a Proxy both wrap something. What is each one's distinct purpose?
tap to reveal →
Facade provides a single simplified high-level interface over a complex subsystem of many classes (e.g. Computer.startComputer() hides CPU.initialize(), HardDrive.readBootSector(), Memory.load(), OS.loadKernel()). Proxy is a stand-in implementing the SAME interface as the real object to control access to it — adding lazy initialization, access control, logging, or caching (e.g. a CachedDatabaseQuery in front of RealDatabaseQuery).
💡 Facade = one start button over many parts. Proxy = gatekeeper/credit-card in front of one object, same interface.
Flashcard
In the Composite Pattern, name the tradeoff (by its canonical GoF terms) for where child-management operations like add(child) are declared.
tap to reveal →
Transparency vs Safety. Transparency: put add/remove on the shared Component interface so leaf and composite are treated uniformly, but the Leaf must throw UnsupportedOperationException (unsafe at runtime). Safety: declare child operations only on the Composite, which is type-safe but the client may need casts and loses uniformity.
💡 Composite = part-whole tree; one component interface so getHours()/totalSize() recurses uniformly. Transparent (uniform, unsafe) vs Safe (typed, less uniform).
Q1. In the Decorator pizza example, PlainPizza costs 10.0, CheeseDecorator adds 2.5, and PepperoniDecorator adds 3.0. What does new PepperoniDecorator(new CheeseDecorator(new PlainPizza())).getCost() return?
Q2. A class has incompatible interface needs growing along two independent axes (e.g. shape type and rendering platform), and you want to avoid an exponential class explosion while letting both axes evolve separately. Which pattern is the best fit?
Q3. In a particle system rendered with the Flyweight Pattern, which attribute set is the EXTRINSIC state that the client must supply at call time?
Q4. In the Proxy Pattern's database caching example, what happens on the SECOND call to executeQuery("SELECT * FROM users") through CachedDatabaseQuery?
Q5. Which statement correctly distinguishes the Adapter Pattern from the Facade Pattern?