Structural patterns
Wrap, combine and simplify objects so incompatible or complex parts fit together.
What you will learn
- 01Structural patterns
- 02Adapter: make an interface fit
- 03Decorator: add behavior by wrapping
- 04Facade: one simple door
- 05Proxy: a stand-in with a purpose
- 06Composite: treat one and many alike
- 07Bridge and Flyweight
- 08Telling them apart
Structural patterns
Structural patterns are about composing classes and objects into larger structures while keeping them flexible. Most work by wrapping: one object stands in front of another and changes how it is seen or used.
Swipe sideways to pan · drag a box to move it
Adapter: make an interface fit
An adapter converts one interface into the one your code expects. Say a legacy bank library takes paise and a reference, while your code wants a simple pay(amount).
interface PaymentGateway { pay(amount: number): boolean }
class BankAdapter implements PaymentGateway {
constructor(private bank: LegacyBank) {}
pay(amount: number) {
return this.bank.makePayment(amount * 100, crypto.randomUUID()) === 0;
}
}
Your code stays unchanged and the awkward library is contained in one class. Use an adapter whenever you integrate something you cannot or should not modify.
Swipe sideways to pan · drag a box to move it
Decorator: add behavior by wrapping
A decorator has the same interface as the object it wraps and adds behavior before or after delegating. Decorators stack.
class DiscountedRule implements PricingRule {
constructor(private inner: PricingRule, private pct: number) {}
fee(m: number) { return this.inner.fee(m) * (1 - this.pct / 100); }
}
const rule = new DiscountedRule(new CarRule(), 10);
You get combinations such as discount plus logging without a class for each. It follows open and closed: new behavior, no edits. The cost is many small wrappers and a harder stack trace.
Swipe sideways to pan · drag a box to move it
Facade: one simple door
A facade offers a small interface over a complicated subsystem. A ParkingFacade with enter and leave hides pricing, slots, tickets and payment from a screen that only wants those two actions. It reduces coupling but must not become a dumping ground for logic.
Proxy: a stand-in with a purpose
A proxy has the same interface as the real object and controls access to it.
- A caching proxy returns stored results.
- A lazy proxy creates the expensive object only when first used.
- A protection proxy checks permissions before forwarding.
- A remote proxy hides that the object lives across a network.
Decorator and proxy look alike. The difference is intent: a decorator adds behavior, a proxy controls access.
Composite: treat one and many alike
A composite lets you treat a single object and a group of objects through one interface, forming a tree.
interface Space { freeCount(): number }
class Slot implements Space { constructor(private free: boolean) {} freeCount() { return this.free ? 1 : 0; } }
class Floor implements Space { constructor(private parts: Space[]) {} freeCount() { return this.parts.reduce((s, p) => s + p.freeCount(), 0); } }
Asking a lot for its free count works the same whether it holds floors, sections or single slots.
Bridge and Flyweight
Bridge separates an abstraction from its implementation so each can vary on its own, such as notification types over several delivery channels. Flyweight shares common data among many objects to save memory, such as one shared description for thousands of identical slots.
Telling them apart
- Wrong interface, need to convert: adapter.
- Add behavior, same interface: decorator.
- Control access to the same interface: proxy.
- Simplify a complex area: facade.
- Treat part and whole alike: composite.
Each adds a layer. Use one when you can name the problem it solves.