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.

KindPurpose
Virtual proxyCreate or load the real object only when needed (lazy loading)
Protection proxyCheck permissions before forwarding
Remote proxyHide that the real object is on another machine (RPC stubs)
Caching or logging proxyAdd 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

NeedPattern
Make an incompatible API fitAdapter
Add behaviour without editing or subclassingDecorator
Simplify a complicated subsystemFacade
Control, defer or guard accessProxy
Treat part and whole alikeComposite

All of them work by delegation: an object that holds another and forwards calls, adding something on the way.

Real-world reading

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

© 2026 forcsguy.com. Original lesson text and diagrams. Books and sites linked above belong to their authors.