2.2.11 · D5Design Principles

Question bank — Design Patterns — Structural - Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

1,905 words9 min readBack to topic
Figure — Design Patterns — Structural -  Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
Figure — Design Patterns — Structural -  Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

Two more terms, defined before their first use:

  • Leaf — in a tree of objects, a node with no children (a single file, an atomic widget). Contrast with a composite, a node that contains children (a folder). See the Composite tree figure:
Figure — Design Patterns — Structural -  Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
  • Class explosion — when you try to cover every combination of two independent choices with a separate class, the count grows as . Bridge and Decorator both exist to stop this. The next figure shows the growth concretely:
Figure — Design Patterns — Structural -  Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

True or false — justify

Adapter and Bridge are the same pattern because both have one object holding another.
False. The code shape is identical, but Adapter is applied after the fact to make already-incompatible code cooperate, while Bridge is designed up front so two dimensions (e.g. shape × colour) can vary independently. Intent and timing differ.
A Decorator must expose the same interface as the object it wraps.
True. That is exactly what makes decorators stackable — the wrapped result is interchangeable with the original, so Logging(Cache(Service())) works with each layer unaware of the others.
A Facade hides its subsystem so clients can no longer reach the inner classes.
False. A Facade simplifies access by offering one door, but it does not forbid advanced clients from reaching the subsystem directly. Hiding entirely would be a different (often over-engineering) choice.
Composite requires that leaves and composites share one common interface.
True. The whole point is uniform treatment: a client calls size() (or render()) without checking whether the target is a single leaf or a whole subtree. Different interfaces would defeat that.
Flyweight reduces the number of method calls your program makes.
False. Flyweight reduces memory, not calls. It shares one copy of the intrinsic (unchanging) state across many logical objects; the calls still happen, but they run against shared instances.
Proxy and Decorator are interchangeable since both wrap and forward.
False. Decorator adds responsibilities and is designed to stack; Proxy controls access (lazy load, caching, permission) to one fixed subject you usually don't choose to stack. Same shape, opposite intent.
Bridge always turns classes into classes.
True in its purpose. By making an abstraction hold a reference to an implementation (composition) instead of inheriting every combination, you add one class per new dimension member rather than one per pair — this is exactly the class-explosion argument worked above ().
Adapter changes the behaviour of the adaptee.
False. Adapter changes the interface (method names/signatures), not the behaviour. It translates play() into start_mp4(); the underlying action is unchanged.
Every structural pattern relies on inheritance more than composition.
False. The 80/20 is the opposite: nearly all of them are composition — a wrapper holding another object and forwarding. This is Composition over Inheritance (assemble at runtime instead of freezing at class time) in action; inheritance appears only to share a common interface.

Spot the error

A junior writes class Mp4Adapter(LegacyMp4) and calls it an Adapter. Error?
The Adapter must implement the target interface (what the client speaks), not inherit the adaptee. Inheriting LegacyMp4 exposes start_mp4, so the client still can't call play. Correct: implement MediaPlayer and hold a LegacyMp4 internally.
Milk.cost() returns 0.5 (a flat charge) instead of self.drink.cost() + 0.5. Error?
It forgot to forward to the wrapped object first. A Decorator's rule is "call inner, then extend"; dropping the inner call breaks stacking — Sugar(Milk(Coffee())) would lose the coffee and the sugar.
A Facade's start() calls disk.read() before mem.load(), in the wrong order. Error?
A Facade's value is orchestration — enforcing the correct call order the subsystem requires. Wrong order defeats the whole reason the client delegated to the facade; the client can't be expected to know the sequence.
Folder.size() does if isinstance(c, File): ... else: .... Error?
This breaks Composite's uniform interface. The point is to call c.size() on every child without checking its type, so leaves and subtrees are handled identically and recursion falls out naturally (see Recursion).
A GlyphFactory creates a new Glyph('a') every time get('a') is called. Error?
It defeats Flyweight — the intrinsic object must be created once and reused from a pool. Without the "if not in pool, create; then return the shared one" check, you get a million glyphs, which is exactly what Flyweight prevents.
A "Bridge" hard-codes self.color = Red() inside Circle.__init__. Error?
The abstraction must receive its implementation (usually via constructor injection) so the two hierarchies stay independent. Hard-coding Red re-couples shape to colour and reintroduces the class explosion Bridge exists to remove. This is Dependency Inversion slipping — the key insight there is "depend on an abstraction (Color) you can swap, not on a concrete class (Red) you cannot".

Why questions

Why does Decorator use composition instead of just subclassing every feature combination?
Subclassing every combination needs a class per pair (MilkSugarCoffee, SugarOnlyCoffee, …) — a combinatorial explosion. Composition lets you build combinations at runtime by wrapping, so m features give 2^m combos with only m decorator classes.
Why must a Decorator wrap and Proxy usually not stack?
A Decorator adds independent responsibilities that compose naturally, so stacking many is meaningful. A Proxy controls access to one specific subject (e.g. lazy-loads this image); stacking access controls is rarely the intent, so it typically wraps a single fixed subject.
Why is Adapter said to respect the Open-Closed Principle?
You make incompatible code cooperate by adding a new adapter class rather than editing the existing (often third-party or legacy) class. The system is open to extension, closed to modification of the untouchable part.
Why does Bridge relate to Single Responsibility Principle?
It separates abstraction (what a shape is) from implementation (how it's coloured) into two hierarchies, each with a single reason to change. Colour logic changing doesn't force shape classes to change, and vice versa.
Why does Composite naturally involve recursion?
A composite contains children that may themselves be composites, forming a tree. Calling size() on a folder delegates to its children's size(), which may again delegate — the definition is self-referential, which is precisely Recursion.
Why does Facade not violate loose coupling even though it touches many classes?
The facade concentrates the coupling in one place, so the client stays loosely coupled to the subsystem. It trades many client-to-subsystem links for one client-to-facade link plus internal orchestration.

Edge cases

What does Composite's size() return for an empty Folder (no children)?
0, because sum(c.size() for c in []) over an empty list is 0. This is the recursion's base-case behaviour and shows a composite with zero leaves is still valid and well-defined.
Can you stack zero decorators — i.e. use the bare Component alone?
Yes. Coffee() with no wrappers is fully valid and returns its base cost. The Decorator pattern degrades gracefully to "just the component" when no extra responsibilities are added.
What happens in a Composite if a Folder contains itself (a cycle)?
size() recurses forever and overflows the stack. Composite assumes a genuine tree (acyclic); cycles are a degenerate misuse the pattern does not defend against on its own.
If two logical characters share one Flyweight glyph, can you safely store their (x, y) on the shared object?
No. Position is extrinsic state and differs per instance; storing it on the shared object would make both characters read the same position. Extrinsic state must be passed in from outside at call time.
What if an Adapter's target interface has a method the adaptee simply cannot support?
The adapter must either emulate it, raise a clear "unsupported" error, or return a safe default — it cannot silently forward, because there is nothing to forward to. This boundary is where a leaky adapter reveals a genuine capability mismatch.
Does a Proxy that lazy-loads still work if the real subject is never used?
Yes, and that's the win — the expensive subject is never created, so the proxy pays zero construction cost for an unused resource. The access-control intent (defer creation) shines exactly in this degenerate "never called" case.
Recall Fast self-test

One phrase that separates all seven patterns ::: All are wrappers that forward; they differ only by intent — translate (Adapter), split two dimensions (Bridge), tree-uniformity (Composite), add behaviour (Decorator), simplify many into one (Facade), share memory (Flyweight), control access (Proxy).


See also: Creational Patterns, Behavioral Patterns.