Design Patterns Deep Dive · 2 of 4

Structural Patterns — How Objects Plug Together

Structural patterns are about composition: how to assemble classes and objects into larger structures while keeping them flexible. They show up everywhere — in HTTP middleware stacks, in ORM query builders, in DOM trees, in remote procedure calls. Most of them you'll recognize the moment you see the diagram, even if you've never named them.

AdapterDecoratorFacadeProxyComposite
← Back to Architecture
Quick Facts

What Structural Patterns Solve

Basic Concepts

  • The problem: two classes have to work together but their shapes don't match — different interfaces, different lifecycles, different access patterns.
  • The solution: introduce a third object that bridges, wraps, or hides the mismatch.
  • The trade-off: indirection. The chain of wrappers is harder to read than direct calls.
  • Where they live: at boundaries — between your code and a third-party SDK, between a domain and its persistence, between an in-process API and a remote one.
The Patterns

Seven Structural Patterns

Adapter

Intent: Wrap an object so it matches a different interface your code expects.

Classic use: integrating a third-party library whose API doesn't match yours. The Adapter is glue; the adapted object is unchanged. You write a thin class that implements your interface and translates each call into the third-party shape.

In hexagonal architecture, "adapters" are exactly this — they wrap REST clients, ORMs, message brokers behind ports the domain owns. The whole architecture is named after this pattern.

Class adapter vs object adapter: class adapter inherits from the adaptee (rare in modern code, requires multiple inheritance or interfaces); object adapter holds a reference to it (the typical form).

Decorator

Intent: Attach additional responsibilities to an object dynamically by wrapping it in another object that implements the same interface.

Decorators implement the same interface as what they wrap, so they nest. Caching, logging, retries, auth, tracing — all natural decorators around an HTTP client, a repository, or a message handler.

HttpClient client = new RetryingClient(
    new LoggingClient(
        new TimeoutClient(
            new BasicHttpClient())));

Compared to inheritance: Decorator stacks behaviors at runtime; subclassing fixes them at compile time. You can mix and match decorators per call site without a combinatorial class explosion.

In the wild: Java's I/O streams (BufferedReader(new FileReader(...))), Python's function decorators, ASP.NET middleware, Express middleware — all decorators with different syntactic dressing.

Facade

Intent: Provide a single, unified interface to a set of interfaces in a subsystem. A Facade defines a higher-level interface that makes the subsystem easier to use.

Behind one method, ten classes coordinate. Useful when you want to expose a small surface area to most callers while still allowing power users to drop down to the underlying APIs.

Example: a VideoConverter.convert(file, format) facade hides the codec lookup, parameter negotiation, encoder pipeline, and metadata copy that would otherwise be 200 lines of imperative glue.

Watch out: facades that grow without bound become god objects. If your facade has 50 methods, it's no longer simplifying — it's just a thicker layer over the same complexity.

Proxy

Intent: Provide a surrogate or placeholder for another object to control access to it.

A Proxy implements the same interface as the real object, but adds something — distance, lazy initialization, access control, caching, instrumentation. Variants:

  • Remote proxy: stub for an object on another machine. RPC, gRPC, RMI client stubs.
  • Virtual proxy: creates the heavyweight resource only when first used. ORM lazy-loaded associations.
  • Protection proxy: auth checks before delegation. Often built into method-level security frameworks.
  • Caching proxy: returns memoized results without calling the real object.
  • Smart reference: reference-counts, locks, or otherwise tracks usage of the underlying object.

Proxy vs Decorator: they look identical from the outside (same-interface wrapper) but Decorator adds behavior; Proxy controls access. The semantics differ even if the structure doesn't.

Composite

Intent: Compose objects into tree structures to represent part-whole hierarchies. Treat individual objects and groups of objects uniformly through a shared interface.

The DOM is a Composite: a single <div> and a tree of nested elements both expose the same node API. Filesystems, organizational charts, UI widget trees, AST nodes, scene graphs — all natural composites.

Example: a render() method on a UI Component. Button.render() draws a button; Panel.render() draws itself and recursively calls render() on its children. The caller doesn't care which.

Bridge

Intent: Decouple an abstraction from its implementation so the two can vary independently.

If you have N abstractions and M implementations, naive inheritance gives you N×M classes. Bridge gives you N + M by separating "what" from "how" with a reference instead of inheritance. Different shapes (Circle, Square, Triangle) × different rendering backends (SVG, Canvas, OpenGL) — three shape classes plus three renderer classes, not nine combined classes.

Reality check: the textbook example is contrived; the real-world cases are subtler — JDBC's Driver abstraction, GUI toolkits' platform-specific peers.

Flyweight

Intent: Share fine-grained objects efficiently to support large numbers of them. Separate intrinsic state (shared) from extrinsic state (passed in per use).

Glyphs in a text editor: one shared object per character code holds the font outline; the per-position state (x/y, color override) lives outside. Otherwise rendering a 1 MB document allocates a million tiny objects.

Modern echo: string interning, Integer.valueOf caching for small ints, immutable value types in databases — all flyweight-flavored sharing.

In Practice

The Patterns You Actually Reach For

In day-to-day modern code, three structural patterns dominate:

  • Adapter — wherever your code meets a third-party API, an adapter sits at the boundary.
  • Decorator — middleware stacks, resilience layers (retry, circuit breaker), instrumentation, caching.
  • Proxy — RPC stubs, ORM lazy loading, security interceptors, caching layers. Often generated by frameworks rather than hand-written.

Composite pops up whenever a tree shows up. Bridge and Flyweight are real but rarer — recognize them when they appear in the libraries you use, but you won't often invent one from scratch.

Continue

Other Pattern Families