Structural Patterns II: Decorator & Composite
Design Patterns
Chapter 5 · Structural Patterns II: Decorator & Composite
Two more ways of composing objects: adding behavior to an object at runtime without touching its class (Decorator), and letting a caller treat one object and a whole tree of objects through the exact same interface (Composite).
Decorator: Adding Behavior Without Modifying the Original
A coffee order needs an arbitrary combination of condiments, each adding its own cost. Decorator wraps the base object in layers, each layer adding one thing:
SimpleCoffee(): 'Coffee', $2.00. MilkDecorator(coffee): 'Coffee, Milk', $2.50. SugarDecorator(MilkDecorator(coffee)): 'Coffee, Milk, Sugar', $2.80 — each layer correctly builds on the layer beneath it.
Milk+Sugar chain above, the original base object still reports 'Coffee' and $2.00 — untouched. Wrapping that same base object in a completely separate chain, WhipDecorator(base), gives 'Coffee, Whip', $2.75 — genuinely different from the Milk+Sugar chain's $2.80, confirming the two decorator chains, built from the identical shared base, don't interfere with each other at all.
Composite: Treating One Object and a Whole Tree Identically
A file has a size. A directory's "size" is the sum of everything inside it — which might itself include other directories. Composite gives both the exact same interface:
project/ containing src/ (two files, 120+80), docs/ (one file, 45, plus a further-nested nested/ directory containing one more file, 30), and a top-level README.md (10): root.get_size() returns 285, matching a manual calculation (120+80+45+30+10) exactly.
.get_size() on a single File (readme.txt, returns 5), on the entire root Directory (returns 285), and on the src Directory alone (returns 200) — the exact same one-line call, with no isinstance check or special-casing anywhere in the calling code, correctly handles a leaf, a shallow branch, and a deeply nested tree alike.
Where This Connects
| This chapter's finding | What it sets up |
|---|---|
| Decorator wrapping without modifying, verified two independent ways | Chapter 6's Proxy reuses the identical wrapping shape again, this time to control access rather than add behavior |
| Composite's verified uniform interface across a nested tree | A concrete, working example of the same "recursion over a self-similar structure" idea pseudocode1's own Chapter 8 (divide and conquer) and Chapter 9 (recursion) already covered |
| Both patterns' zero-modification-to-existing-code discipline | Clean Code, SOLID & Refactoring's own Open/Closed Principle, stated formally |
Hands-On Exercises
Using this chapter's own decorator classes, build a chain representing "Coffee, Whip, Milk, Sugar" (in that wrapping order) and compute its total cost by hand from each decorator's own added amount, then verify it against running the actual code.
📄 View solutionUsing this chapter's own File/Directory classes, add one more file (notes.txt, size 15) directly into the docs directory from this chapter's own worked example, and compute the new total root.get_size() both by hand and by running the code.
Using this chapter's own two patterns, explain why Decorator's own base-object-stays-unmodified guarantee and Composite's own uniform-interface guarantee are solving genuinely different problems, even though both patterns involve one object containing a reference to another.
📄 View solutionChapter 5 Quick Reference
- Decorator: wraps an object to add behavior at runtime, without modifying its class — verified accumulating correctly (
$2.00 → $2.50 → $2.80), leaving the base object untouched, and keeping separate chains from the same base independent ($2.80vs.$2.75) - Composite: a shared interface (
get_size()) lets a caller treat a single leaf and a whole nested tree identically — verified: a 3-level-deep tree totaled285, matching a manual calculation exactly, using the same one-line call at every level with zero type-checking - Next chapter: Structural Patterns III — Proxy and Flyweight