Handbooks / OOP & Design Patterns / Chapter 4
Structural Patterns
7 pages · ~5 min✓ Reviewed
Builds on Creational Patterns. Next up: Behavioral Patterns.
Structural Patterns Mental Model
1. One Skeleton
- Compose classes and objects into bigger structures; behaviour contracts stay unchanged
- Favour composition over inheritance
- Wrapper holds a reference to the wrapped object and forwards calls
- Patterns differ in intent, not in shape
- Client
- Wrapperholds a reference
- Wrappeddoes the work
2. Interface Decides the Pattern
| Wrapper's interface | Patterns |
|---|---|
| Changes | Adapter, Facade |
| Identical | Decorator, Proxy, Composite |
| Split abstraction from implementation | Bridge |
3. One-Line Intents
| Pattern | Intent |
|---|---|
| Adapter | converts one interface to another |
| Decorator | adds behaviour |
| Facade | simplifies a subsystem |
| Proxy | controls access |
| Composite | treats tree and leaf alike |
| Bridge | decouples abstraction from implementation |
Quick test for look-alikes
Ask what the wrapper is for: convert, add, simplify, guard, aggregate. Not how it is built.
Adapter and Facade: Changing the Interface
1. Adapter
- Client expects Target; existing Adaptee has an incompatible interface
- Adapter implements Target, translates each call to the Adaptee
- Object adapter: holds adaptee by composition, preferred, works with subclasses
- Class adapter: inherits adaptee via multiple inheritance, rare
typescript
class XmlToJsonAdapter implements JsonSource { constructor(private xml: XmlSource) {} read() { return toJson(this.xml.read()); } }
object adapter: translates read()
2. Facade
- One simplified entry point over a complex subsystem
VideoConverter.convert(file, 'mp4')hides codec, bitrate and audio classes- Subsystem classes stay publicly usable
3. Adapter vs Facade
| Adapter | Facade | |
|---|---|---|
| Goal | Match an interface the client already expects | Define a new, simpler interface |
| Wraps | One existing class | Many classes |
God object
A Facade that grows business logic becomes a god object. Keep it a thin delegator.
Decorator and Proxy: Same Interface, Different Intent
1. Decorator: add behaviour
- Wraps an object with the same interface, adds work before or after delegating
- Decorators stack; the client picks the order
- Java:
new BufferedReader(new FileReader(f)) - Python:
@decoratoradds logging or caching around a call
new Gzip(new Encrypt(new FileStream()))
- 1Gzipcompress
- 2Encryptthen encrypt
- 3FileStreamwrites
2. Proxy: control access
- Stand-in with the same interface that guards the real object
- Virtual: lazy-load the costly object
- Protection: permission check
- Remote: network stub
- Caching: reuse earlier results
3. Decorator vs Proxy
| Decorator | Proxy | |
|---|---|---|
| Purpose | Add features | Control access |
| Chosen by | Client stacks it | Often invisible to client |
| Real object | Passed in | Proxy manages lifecycle |
4. Mistakes
- Order matters: compress then encrypt, since ciphertext barely compresses
- Proxy that swallows errors or breaks the interface
- Subclass per feature combination: class explosion, use decorators
Quick test
Ask what the wrapper is for: add behaviour is Decorator, guard access is Proxy.
Composite and Bridge: Hierarchies and Abstractions
1. Composite: Tree as One Object
- Component interface shared by Leaf and Composite
- Composite holds child Components, forwards calls
- Client treats one object and a tree alike
python
class File: def size(self): return self.bytes class Folder: def size(self): return sum(c.size() for c in self.children)
Folder recurses; File is the base case
add(),remove()on Composite: safe- On Component: transparent, leaves fail
- Trade-off: transparency vs safety
2. Bridge: Two Dimensions
- Abstraction and Implementor hierarchies joined by composition
- Shape with Renderer: Circle/Square x Vector/Raster
- M+N classes, not M×N
- Circle, SquareAbstraction
- Shapeholds a Renderer
- Vector, RasterImplementor
| Bridge | Adapter | |
|---|---|---|
| Built | Up front | After the fact |
| Goal | Split two dimensions of change | Make incompatible classes fit |
3. Mistakes
- Bridge when only one dimension varies
- Mixing up Strategy: it swaps an algorithm, Bridge a whole implementation hierarchy
Choosing Between the Six Patterns
1. Pick by Intent
| Need | Pattern |
|---|---|
| Incompatible interface | Adapter |
| Simplify a subsystem | Facade |
| Add behaviour dynamically | Decorator |
| Control access or lazy-load | Proxy |
| Tree of uniform parts | Composite |
| Two independent dimensions of variation | Bridge |
samenew
2. In Real Code
| Example | Pattern |
|---|---|
Java BufferedReader over FileReader | Decorator |
JS Proxy object | Proxy |
| React component tree | Composite |
| jQuery over the DOM | Facade |
| ORM driver layer | Bridge |
| Node stream wrappers | Decorator |
3. Limits and Combos
- One implementation only: skip the pattern
- A plain function or direct call is enough: skip it
- Facade may use Adapters inside
- Composite tree may be wrapped by Decorators
- Proxy can front a Facade
Quick test
Ask what the wrapper is for, not how it is built.
Structural Patterns Cheat Sheet
1. Wrappers at a Glance
| Pattern | Intent | Interface | Example |
|---|---|---|---|
| Adapter | convert A to B; wraps one object | changes | legacy SDK wrapper |
| Facade | simplify a subsystem | new, simpler | VideoConverter.convert() |
| Decorator | add behaviour; stackable | same | Gzip(Encrypt(Stream)) |
| Proxy | control access | same | lazy image loader |
- Proxy kinds: virtual, protection, remote, cache
2. Composite and Bridge
| Composite | Bridge | |
|---|---|---|
| Idea | tree: leaf and group share one interface | abstraction and implementation vary independently |
| Payoff | client treats one and many alike | M+N classes, not M×N |
Quick test
Ask what the wrapper is for: convert, add, simplify, guard, aggregate, not how it is built.
Check yourself
You call Gzip(Encrypt(FileStream())).write(data). In what order is the data transformed?
- Compressed first, then encrypted: the outer decorator runs first, then delegates inward.
- Swapping the order would compress ciphertext, which barely shrinks.
ReportProxy implements Report and wraps RealReport. Its load() catches every exception and returns an empty report. What is wrong?
- It swallows errors, so callers can no longer tell failure from an empty result.
- A proxy must keep the real object's contract, not hide its failures.
3 shapes (Circle, Square, Triangle) and 3 renderers (Vector, Raster, Svg). How many concrete classes with Bridge, and with plain subclassing?
- Bridge: 3 + 3 = 6 concrete classes.
- Subclassing: 3 × 3 = 9, and every new shape adds a whole row.