Design Patterns

Sefism.

Chapter 03

Structural patterns

The seven patterns that compose classes and objects into larger structures while keeping them flexible.

Adapter

Also called wrapper, translator

What
Intent: convert the interface of an existing class into the one your code expects.
Why
When: plugging in a third-party library or legacy module whose method names and shapes do not match yours.
TypeScript
interface Sms { send(to: string, text: string): void; }

class VendorSmsAdapter implements Sms {
  constructor(private api: VendorClient) {}
  send(to: string, text: string) {
    this.api.dispatchMessage({ msisdn: to, body: text });
  }
}

Bridge

Also called handle and body

What
Intent: split an abstraction from its implementation so the two can vary independently.
Why
When: two dimensions would multiply subclasses, such as shapes times renderers, or remotes times devices.
TypeScript
interface Renderer { circle(r: number): void; }

class Circle {
  constructor(private r: number, private out: Renderer) {}
  draw() { this.out.circle(this.r); }
}
new Circle(5, new SvgRenderer()).draw();
new Circle(5, new CanvasRenderer()).draw();

Composite

Also called tree structure, part-whole

What
Intent: compose objects into trees and treat single objects and groups the same way.
Why
When: data is naturally a tree, such as files and folders, UI components or an organisation chart.
TypeScript
interface Node { size(): number; }

class File implements Node {
  constructor(private bytes: number) {}
  size() { return this.bytes; }
}
class Folder implements Node {
  children: Node[] = [];
  size() {
    return this.children.reduce((s, c) => s + c.size(), 0);
  }
}

Decorator

Also called wrapper

What
Intent: add behaviour to an object by wrapping it in another object with the same interface.
Why
When: you need combinations of extras, such as logging, caching and retries, without a subclass for every mix.
TypeScript
interface Store { get(k: string): Promise<string>; }

class CachedStore implements Store {
  private cache = new Map<string, string>();
  constructor(private inner: Store) {}
  async get(k: string) {
    if (!this.cache.has(k))
      this.cache.set(k, await this.inner.get(k));
    return this.cache.get(k)!;
  }
}

Watch out

Wrapping so many layers that the order of decorators becomes a hidden source of bugs.

Facade

Also called simplified interface

What
Intent: one simple entry point in front of a complicated subsystem.
Why
When: callers need a common task done without learning five classes, such as "place order" over stock, payment and email.
TypeScript
class Checkout {
  constructor(private stock: Stock, private pay: Payments,
              private mail: Mailer) {}
  async placeOrder(cart: Cart) {
    await this.stock.reserve(cart.items);
    await this.pay.charge(cart.total);
    await this.mail.receipt(cart.email);
  }
}

Flyweight

Also called shared state, intrinsic and extrinsic state

What
Intent: share the common, unchanging part of many similar objects instead of storing it in each one.
Why
When: you have huge numbers of small objects, such as characters in an editor or trees in a game map, and memory matters.
Example
A forest of a million trees stores the mesh and texture once per species; each tree keeps only its position and size.

Proxy

Also called surrogate, lazy loading, protection proxy

What
Intent: a stand-in object with the same interface that controls access to the real one.
Why
When: you need lazy loading, access checks, remote calls or rate limiting without changing the real class or its callers.
TypeScript
class AuthorisedReports implements Reports {
  constructor(private real: Reports, private user: User) {}
  export(id: string) {
    if (!this.user.isAdmin) throw new Error('Forbidden');
    return this.real.export(id);
  }
}

Watch out

Confusing it with Decorator: a proxy controls access to the same behaviour, a decorator adds behaviour.

03 · Structural patterns3 / 9