Seven structural patterns later, it's time for the same thing we did when closing the creational family: laying them out on the table together, which is where the real deciding happens. And in this family the comparison is more necessary than in any other, because four of its members —Adapter, Decorator, Facade, and Proxy— are mechanically almost indistinguishable: one object in front of another, delegating. Choosing wrongly among them doesn't break the program; it breaks something more expensive: the design's communication, because a pattern's name is a promise about intent, and a broken promise misleads everyone who comes after. General table, wrapper face-off, decision guide, combinations, and the complete structural map of PideYa: let's get to it.

Contents

  1. The seven, face to face
  2. The four wrappers: same mechanics, different intents
  3. Decision guide
  4. How they combine with each other
  5. PideYa's structural map
  6. Common mistakes, exercises, and conclusion

The seven, face to face

The overview table from the introduction, now enriched with what we've learned: the symptom that triggers each pattern and the cost you pay.

Pattern Intent in one sentence Triggering symptom Main cost
Adapter Translate the interface of an untouchable piece into the one the client expects Incompatible interfaces with foreign/legacy code One translator class per adaptee; watertight-border discipline
Bridge Split abstraction and implementation into two hierarchies connected by composition Compound-named subclasses (ConfirmationPush): two fused axes multiplying Up-front design; one indirection; two hierarchies to maintain
Composite Treat leaves and groups uniformly in part-whole trees The same if (isGroup) repeated in every operation A common interface to agree on; the transparency/safety dilemma; beware cycles
Decorator Add responsibilities to an object by wrapping it, combinable at runtime Subclass explosion from combinations of optional extras Opaque onion; many small classes; layer order matters
Facade Offer a simple door in front of a complex subsystem The same orchestration copied into several clients Risk of fattening up; temptation to smuggle in business logic
Flyweight Share the intrinsic state of crowds of objects Thousands of instances duplicating heavy data, memory measured at its limit Splitting intrinsic/extrinsic complicates signatures; demands immutability
Proxy A stand-in with the same interface that controls access to the real object An expensive, sensitive, remote, or repeatedly-queried object with uncontrolled access Invisible indirection; contract fidelity; caches to invalidate

Two cross-cutting axes for ordering them mentally:

  • How many objects does it organize? One in front of another: Adapter, Decorator, Proxy. Many behind one: Facade (a subsystem), Composite (a tree), Flyweight (a crowd). Two entire hierarchies: Bridge.
  • Does it change the interface the client sees? It changes it: Adapter. It simplifies it: Facade. It keeps it exactly: Decorator, Proxy, Composite (the group offers the leaf's). It splits it in two: Bridge. It doesn't care: Flyweight (its topic is memory, not the interface).

The four wrappers: same mechanics, different intents

The star face-off. All four share the gesture —receiving calls addressed to another object and deciding what to do with them— and differ in what they promise:

Aspect Adapter Decorator Facade Proxy
Resulting interface Different from the wrapped one (the one the client expected) The same as the wrapped one New and simpler than the subsystem's The same as the wrapped one
What it promises Faithful translation, nothing more The wrapped object plus visible responsibilities The common use case without knowing the guts The wrapped object, with access watched
Alters observable behavior? No (only the language) Yes: it adds, and that's the point Adds no rules: coordinates the existing ones May refuse, postpone, or answer from memory
Wraps... 1 foreign/legacy object 1 object of ours, often already wrapped (layers) N objects (the subsystem) 1 object of ours, usually the "real" one
Who puts it there? The integrator, at the border The client or the composer, stacking à la carte The architect, as the module's door The infrastructure, without the client knowing
In PideYa PayPalAdapter DoubleCheeseExtra, RetryNotifier CheckoutFacade DishImageProxy, OrderManagementGuardProxy

Four questions in order, which settle 90% of the classification doubts:

  1. Is the outside interface different from the inside one? → Adapter (if it is simpler and bundles several, → Facade).
  2. Same interface: does the wrapper add visible behavior the client wants and combines? → Decorator.
  3. Same interface: does the wrapper decide about access (when, who, how many times, where) without the client taking part? → Proxy.
  4. Still in doubt (some logging, some metrics)? You are on the Decorator/Proxy border we already worked through in the Proxy lesson: decide by who composes it and for what — an optional layer stacked by the developer, Decorator; a control interposed as a matter of course by the infrastructure, Proxy — and document the decision. The border exists; pretending it doesn't only confuses more.

Decision guide

The whole module's question flow. Like the creational guide: it orients the typical case, and the preliminary filter still applies — is there truly a symptom, or are we decorating for sport?

flowchart TD
    A[Problem composing<br/>or organizing objects] --> B{Is it about MEMORY?<br/>thousands of objects, repeated data,<br/>profiler in the red}
    B -- Yes --> FW[Flyweight<br/>shared intrinsic state + factory]
    B -- No --> C{Is the domain structure<br/>a part-whole TREE?}
    C -- Yes --> CP[Composite<br/>uniform leaves and groups]
    C -- No --> D{Does a hierarchy grow as the<br/>PRODUCT of two independent<br/>axes?}
    D -- Yes --> BR[Bridge<br/>abstraction × implementation]
    D -- No --> E{Is the problem about<br/>ONE object I put<br/>something in front of?}
    E -- No --> F{Do many clients repeat the<br/>orchestration of a subsystem?}
    F -- Yes --> FC[Facade<br/>high-level door]
    F -- No --> Z[Maybe it isn't a structural<br/>problem: revisit creational<br/>or wait for module 4]
    E -- Yes --> G{Is its interface NOT<br/>the one I need?}
    G -- Yes --> AD[Adapter<br/>translate at the border]
    G -- No --> H{Do I want to ADD combinable,<br/>visible behavior?}
    H -- Yes --> DC[Decorator<br/>stackable layers]
    H -- No --> PX[Proxy<br/>control access:<br/>virtual, protection, cache, remote]

Usage tips, as always: several answers may apply at once (a subsystem behind a facade can contain a composite cached by a proxy); the guide is applied per design decision, not per system; and if no branch fits, the "maybe it isn't structural" exit is a legitimate answer — problems of communication and runtime distribution of responsibilities belong to the module ahead.

How they combine with each other

Like the creational patterns, the structural ones work as a crew. The combinations you have already seen or sniffed in PideYa:

  • Composite + Decorator: they share a Component interface by design; a decorator can wrap any node of the tree. The extras (DoubleCheeseExtra) decorate dishes living in the Composite menu; to the client, both are Product/MenuComponent.
  • Composite + Flyweight: the repeated leaves of a huge tree are shared as flyweights (the GoF points this out explicitly); in PideYa, if every restaurant of a franchise repeats the same base menu, the shared Dish objects are candidates.
  • Proxy + Composite: CatalogCacheProxy returns complete menu trees: the proxy controls the access; the composite structures what is accessed.
  • Facade over all of them: CheckoutFacade coordinates a subsystem where the market Abstract Factory, the Order Builder, gateway adapters, and decorated notifiers already work. The facade doesn't compete with them: it hides them.
  • Adapter inside Bridge: a concrete implementor (WhatsAppChannel) can internally be an adapter of the provider's SDK. The bridge gives the shape; the adapter, the border.
  • Decorator/Proxy stacked together: logging outside retries, protection inside logging... Layer order is semantics (what gets logged, what gets retried): you verified it in both lessons' exercises.
  • Creational patterns in service of structural ones: factories assemble the structures — they decide whether to hand out the real object or its proxy, compose the standard decorator stack, choose the market's adapter. Module 2's sentence lives on: each pattern governs one layer of the decision.

PideYa's structural map

The final system review, just as we closed the creational module: what got installed, where, and why — with the usual last row, the most important one.

Part of PideYa Structural solution Why that one and not another
Charges through the legacy PayPal SDK Adapter (PayPalAdapter implements PaymentGateway) A foreign, untouchable interface that had to look like just another gateway; all the customs work in one place
Catalog from the external aggregator Adapter (AggregatorAdapter) Same symptom, another border: a third-party model translated into ours
Notifications: types × channels Bridge (NotificationDeliveryChannel) Two independent axes growing as a product (9 classes) → additive growth (3+3)
The restaurant's menu Composite (MenuComponent, Dish, MenuSection), safe variant A part-whole tree of unbounded depth with uniform, recursive operations
Combos and nested combos Composite (Combo, recursive price with discount) The group is also for sale; allMatch versus anyMatch as the business dictates
Dish extras Decorator (DishExtra: double cheese, gluten-free, large portion) Optional combinatorics decided by each customer at runtime; 3 classes cover 8 combinations
Notifier retries and traces Technical Decorator (RetryNotifier, LoggingNotifier) A cross-cutting, optional, composable responsibility without touching the channels
Order confirmation flow Facade (CheckoutFacade) Delicate orchestration copied into four clients → written once behind a simple door
Order tracking in the apps Facade (TrackingFacade) Same symptom at a smaller scale; boundary-owned types
Real-time map Flyweight (MarkerIcon + IconFactory) 13,000 markers repeating ~25 icon combinations: from 400 MB to under 1
Dish photos Virtual Proxy (DishImageProxy) An expensive, sparsely used object: you pay only for what you look at
Internal panel permissions Protection Proxy (OrderManagementGuardProxy) The who concentrated in one auditable point; the real service, clean of security
Massive menu queries Caching Proxy (CatalogCacheProxy) An expensive, stable answer requested thousands of times; invalidation designed alongside
Cross-cutting traces over many interfaces Dynamic Proxy (LoggingHandler + java.lang.reflect.Proxy) A single handler for all interfaces; the frameworks' mechanism
Orders, carts, lines, addresses... None Objects in normal quantities, interfaces that fit, no multiplying axes: any wrapper here would be noise

That is the maturity criterion we have been repeating since the trade-off lesson: not how many patterns the system has, but that each one sits where its symptom justified it — and that the rest of the code stays simple.

Common Mistakes and Tips

  • Naming by mechanics, not by intent. Calling an adapter a "proxy" or a facade a "decorator" compiles just the same, but it lies to the reader about the promise (does it translate? add? control?). In this family, the pattern's name is first-class documentation: spend it well.
  • Wrapping out of habit. After this module, the temptation is to layer everything. Every wrapper is an indirection someone will have to debug; without a symptom (an interface that doesn't fit, an optional responsibility, access to control), the best structure is none.
  • Solving with inheritance what this module solves with composition. The GlutenFreeMargarita or ConfirmationPush subclass will always be within reach and will always look faster. Remember the two explosions (Decorator and Bridge cure them) before inheriting the next variant.
  • Choosing Bridge when Adapter was due, or vice versa. If the pieces already exist and don't fit: Adapter, and move on. If you design the hierarchies yourself and they will grow along two axes: Bridge. Designing "bridges" toward foreign SDKs or "adapting" your own hierarchies are the two disguises of the same mix-up.
  • Facade as a hiding place. If behind the facade the subsystem is a knot, the knot keeps growing — now with nobody watching. The facade crowns a healthy subsystem; it doesn't substitute for one.
  • Tip: repeat here the mapping exercise you did with the creational patterns: list every border, every hierarchy, and every crowd in your system, and note which structural pattern (or which absence) governs it and why. Rows without justification are your candidates for simplification; borders without an adapter and copied orchestrations, your candidates for improvement.

Exercises

Exercise 1: express diagnosis

For each new situation in PideYa, pick the structural pattern (or none) and justify it in one sentence with its discriminant:

  1. A partner's invoicing service demands orders in its EDI format, very different from our Order model.
  2. Coupons apply stackable surcharges/discounts on an order's price: free delivery, -10% first purchase, +€0.50 at rush hour, combinable per campaign.
  3. Delivery zones are organized into districts containing neighborhoods containing streets, and we must know whether an address falls in a surcharged zone, at any level.
  4. The new "one-tap reorder" mobile widget must repeat the last order: today it requires calling five services in sequence.
  5. Each of the 2 million customers stores its own NotificationPreferences object, and 97% have exactly the default configuration.
  6. We want the geocoding service to be instantiated (it loads a 2 GB index) only if some order of the day needs to resolve a new address.

Exercise 2: the face-off in code

Without any context, a colleague finds this class and asks which pattern it is. What do you answer, and what additional information would you request to settle it completely?

public class MonitoredDeliveryService implements DeliveryService {
    private final DeliveryService inner;
    private final Clock clock;

    @Override
    public DeliveryAssignment assign(Order order) {
        long start = clock.now();
        DeliveryAssignment a = inner.assign(order);
        Metrics.record("assign", clock.now() - start);
        return a;
    }
}

Exercise 3: combining wisely

The partners team wants to expose menu lookups to third parties: a simple public API, without revealing the internal model, with fast responses even when the catalog is under load, and with no queries allowed from a partner without an active contract. Propose the (minimal) combination of structural patterns and the order they are traversed in a request.

Solutions

Solution 1:

  1. Adapter: a foreign, untouchable format at the border; an EdiBillingAdapter translates Order → EDI and nothing more.
  2. Decorator: optional surcharges/discounts, stackable and with meaningful order over a price interface — the extras onion, in the coupons domain.
  3. Composite: a recursive part-whole (district ⊃ neighborhood ⊃ street) with one uniform operation (hasSurcharge(address)), answered by leaves and propagated by groups.
  4. Facade: orchestration of several subsystems repeated from yet another client; a QuickOrderFacade (which internally will reuse module 2's Prototype for "repeat order").
  5. Flyweight: a crowd (2M) with massively repeated state (97% identical and immutable): one shared instance of the default preferences, and objects of their own only for those who customize.
  6. Virtual Proxy: an extremely expensive object to instantiate and possibly never used; the proxy materializes the geocoder on first real use.

Solution 2: the short answer: "by mechanics it's a wrapper with the same interface; by intent, measuring calls sits on the Decorator/Proxy border". Information needed to decide: who composes it and how mandatory it is. If it is the infrastructure that always interposes it in production, without any client choosing it (control as a matter of course): call it a proxy (metrics/logging). If it is an optional layer that each usage point stacks or not, combining it with others (retries, traces): call it a technical decorator. It also helps to know whether a family of stackable wrappers exists (smells like Decorator) or this one is unique and fixed in front of the real service (smells like Proxy). The important part —tell them this too— is that the class is fine: the doubt is about the label, not the design.

Solution 3: three pieces, from the outside in: (1) Facade PublicMenuApi: a minimal surface for partners, with boundary-owned types (nothing from the internal model; the face-off taught us that a new, simpler interface over a subsystem = facade). (2) A protection Proxy over the catalog service: verifies the partner's active contract and denies with logging (the who). (3) A caching Proxy (CatalogCacheProxy, reused): stable answers served from memory (the how many times). Order of a request: facade → protection → cache → real catalog, which returns the Composite tree of the menu (fourth piece, already existing, free). Protection before cache: a delinquent partner shouldn't even warm the cache — wrapper order is semantics once again.

Conclusion

You no longer have seven loose patterns but a decision system: the table of intents and costs, the two axes (how many objects? what happens to the interface?), the four-wrapper face-off with its four questions, the flowchart for the typical case, and the combinations that appear on their own when the design is going well. And you have PideYa's map as a demonstration of the whole course's thesis: each pattern installed where a concrete symptom justified it, none where it didn't — with the "None" row defended as proudly as the rest.

With this, two thirds of the GoF catalog are in your toolbox: the creational patterns solved objects' birth; the structural ones, their anatomy — how they connect, wrap, and organize. The third question remains, the most dynamic of all: with objects created and well connected, how do they collaborate at runtime? Who tells whom when an order changes state, how is an action undone, how does the kitchen walk a queue of tickets without knowing its structure, where does the algorithm live that decides the delivery? That is the largest family in the catalog —eleven behavioral patterns— and PideYa is full of conversations waiting for them. See you in the Introduction to Behavioral Patterns.

© Copyright 2026. All rights reserved