Category
Structural Patterns
Patterns that combine objects and classes into larger structures while keeping each part replaceable and its interface small.
What these patterns solve
Structural patterns deal with how pieces fit together. One interface doesn't match another, a subsystem is too wide to call directly, a feature must be layered on without editing the class, or millions of small objects use too much memory. Each pattern puts a wrapper, a tree or a shared object between the caller and the real work.
Most structural patterns look the same in code: an object that holds another object and forwards calls to it. Tell them apart by intent, not by shape.
Members
- Adapter: converts one interface into the interface a caller expects, such as wrapping a vendor SDK behind your own port.
- Bridge: splits an abstraction from its implementation so both can vary independently, avoiding a class for every combination.
- Composite: treats a single item and a tree of items through the same interface, as with files and folders.
- Decorator: wraps an object to add behavior (caching, retries, logging) while keeping its interface.
- Facade: gives a complex subsystem one small, task-oriented entry point.
- Flyweight: shares the immutable part of many similar objects to cut memory use.
- Proxy: stands in for another object with the same interface to control access: lazy loading, remote calls or permission checks.
How to choose within this category
- The interface is wrong and you don't own the other side: use Adapter.
- The interface is fine but you want extra behavior around calls: use Decorator. If the extra behavior is about whether or when the real object is reached, it's a Proxy. In languages with higher-order functions, wrapping a single function (
withRetry(fetchInvoice)) often replaces a decorator class. - The subsystem is too big for most callers: use Facade. It adds a simpler interface; it doesn't translate an incompatible one.
- You have nested parts and wholes: use Composite. A recursive union type with pattern matching can replace it in TypeScript, Rust or modern Java.
- Two dimensions vary and subclasses multiply (
PdfInvoice,HtmlInvoice,PdfReceipt, ...): use Bridge. - Profiling shows memory pressure from duplicated state: use Flyweight. Don't apply it without a measurement.
Prefer no pattern when
- You control both sides of an interface mismatch. Change one side instead of adding an adapter.
- A wrapper would forward every call unchanged. That's a Middle Man.
- Only one combination of features ever exists. Compose it directly.
See also
- Creational PatternsPatterns that control how objects get built, so callers depend on what they need rather than on which concrete class supplies it.
- Behavioral PatternsPatterns that assign responsibilities and route requests between objects, so behavior can change without rewriting the code that triggers it.
- Choosing a PatternA decision guide that maps concrete code symptoms to the pattern that fixes them, and tells you when plain code beats any pattern.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.