Structural patterns: Adapter, Decorator, Facade, Proxy
How to compose objects and wrap existing code so pieces fit together, add behaviour and hide complexity.
On the roadmap:Software Design · step 2: Design patterns
Lesson 4 of 7 · 10 min read · quiz · exercise
The goal
Structural patterns are about composing classes and objects into larger structures, often by wrapping existing ones, without changing them.
Adapter
Make an existing class fit the interface you need.
// Your code expects this
interface Gateway { charge(cents: number, token: string): Promise<{ ok: boolean }> }
// A third-party library with a different API
class AcmePay { makePayment(req: { amount: number; source: string }): Promise<{ status: string }> {/*...*/} }
class AcmeAdapter implements Gateway {
constructor(private acme: AcmePay) {}
async charge(cents: number, token: string) {
const r = await this.acme.makePayment({ amount: cents / 100, source: token });
return { ok: r.status === "approved" };
}
}
Use it to wrap third-party and legacy code, and to keep vendor details out of your core. Swapping providers then means writing one new adapter.
Decorator
Add behaviour by wrapping an object that has the same interface. Wrappers stack in any order.
interface Weather { forecast(city: string): Promise<string> }
class Logged implements Weather {
constructor(private inner: Weather) {}
async forecast(city: string) { console.time(city); const r = await this.inner.forecast(city); console.timeEnd(city); return r; }
}
class Cached implements Weather {
private cache = new Map<string, string>();
constructor(private inner: Weather) {}
async forecast(city: string) {
if (!this.cache.has(city)) this.cache.set(city, await this.inner.forecast(city));
return this.cache.get(city)!;
}
}
const service: Weather = new Logged(new Cached(new RealWeather()));
Typical uses: logging, caching, retries, authentication, compression, metrics. HTTP middleware is this idea.
Decorators stack like layers
Figure SDA-SDE-04-1 · Wrapping a service with logging and caching · © forcsguy.com
Facade
A simple front over a complex subsystem.
class VideoConverter { // the facade
convert(file: string, format: "mp4" | "webm") {
const audio = new AudioExtractor().extract(file);
const frames = new FrameDecoder().decode(file);
return new Encoder().encode(frames, audio, format);
}
}
Callers use convert, not five classes. It reduces coupling and gives you a stable surface to keep when internals change. Do not let it become a "god object" that does everything.
Proxy
A stand-in with the same interface that controls access to the real object.
| Kind | Purpose |
|---|---|
| Virtual proxy | Create or load the real object only when needed (lazy loading) |
| Protection proxy | Check permissions before forwarding |
| Remote proxy | Hide that the real object is on another machine (RPC stubs) |
| Caching or logging proxy | Add cross-cutting behaviour |
The structure matches a decorator; the intent differs: control access versus add behaviour.
Composite
Treat individual objects and groups of them uniformly, usually as a tree.
interface Node { size(): number }
class File implements Node { constructor(private bytes: number) {} size() { return this.bytes; } }
class Folder implements Node {
constructor(private children: Node[]) {}
size() { return this.children.reduce((sum, c) => sum + c.size(), 0); }
}
A caller asks any node for its size() without caring whether it is a file or a folder. Used for file systems, UI component trees and organisation charts.
Bridge and Flyweight (briefly)
- Bridge: split an abstraction from its implementation so each can vary independently. Example: shapes (circle, square) times renderers (screen, PDF), without a class for each combination.
- Flyweight: share the common, unchanging part of many similar objects to save memory (for example one glyph shape reused for every occurrence of a letter).
Choosing
| Need | Pattern |
|---|---|
| Make an incompatible API fit | Adapter |
| Add behaviour without editing or subclassing | Decorator |
| Simplify a complicated subsystem | Facade |
| Control, defer or guard access | Proxy |
| Treat part and whole alike | Composite |
All of them work by delegation: an object that holds another and forwards calls, adding something on the way.
Real-world reading
Related reading
- ByteByteGo: System Design Video SeriesAlex Xu · ByteByteGoYouTube
- tCache: Scalable Data-aware Java Caching at TrivagoTrivago engineers · TrivagoCase Studies
- Cross-Region Caching Library at WixWix engineers · WixCase Studies
- Standardize and Improve Microservices Caching at DoorDashDoorDash engineers · DoorDashCase Studies
Show 6 moreShow fewer
- Pycache: In-process Caching at QuoraQuora engineers · QuoraCase Studies
- Scaling Memcache at Facebook (paper)Nishtala et al. · Meta (Facebook)Papers
- Prefetch Caching of Items at eBayeBay engineers · eBayCase Studies
- ByteByteGo NewsletterAlex Xu and Sahn Lam · ByteByteGoPodcasts & Newsletters
- Logging and Aggregation at QuoraQuora engineers · QuoraCase Studies
- A 360 Degree View of the Entire Netflix Stack - High Scalability -High Scalability engineers · High ScalabilityCase Studies
Check your understanding
Question 1 of 3
0 correct so far
You use a payment library whose interface does not match your own PaymentGateway interface. Which pattern fits?
Go deeper
- Refactoring Guru: structural patterns by Refactoring Guru
Practise it: URL shortener
Everything below is already set up for this lesson: change the design, run the traffic, and see whether you meet the goal.
- 1The task
- 2Build and test
Read-heavy, a few links are very popular.
Add or remove parts, move traffic up and down, and break something to see what gives way first.
Try it yourself
You want to add caching and logging around a slow `WeatherService` without editing it, and callers must not change. Which pattern, and how would you structure it?
Click what a strong design here would need:
More in Design practice
© 2026 forcsguy.com. Original lesson text and diagrams. Books and sites linked above belong to their authors.