Chapter 4: Structural Patterns
Structural patterns focus on how parts of a system connect. They help you arrange components so that your design stays flexible even as it grows. Each pattern introduces a different way to link objects, wrap behaviour or combine pieces into meaningful structures that stay easy to reason about.
Adapter
Adapter lets incompatible interfaces work together by placing a small translation layer between them. It changes the shape of one object so that it matches what another part of the system expects.
Intent
Convert one interface into another so that existing code can use a new class without modification.
Structure
An adapter holds a reference to an adaptee. When clients call methods on the adapter, it forwards those calls to the adaptee using names or formats the adaptee understands. The client sees a clean and expected interface.
Pseudocode
class LegacyPrinter {
method old_print(text) { … }
}
interface Printer { method print(text) }
class Adapter implements Printer {
constructor(adaptee)
method print(text) {
adaptee.old_print(text)
}
}
This keeps the legacy code untouched while giving new clients a compatible entry point.
Python
class LegacyPrinter:
def old_print(self, text):
return f"Legacy: {text}"
class PrinterAdapter:
def __init__(self, adaptee):
self.adaptee = adaptee
def print(self, text):
return self.adaptee.old_print(text)
When to Use
Use Adapter when you must reuse existing components that have incompatible interfaces or when you want to smooth over library inconsistencies.
Tradeoffs
You gain compatibility with minimal intrusion; you pay with an extra layer of indirection.
Bridge
Bridge separates an abstraction from its implementation so that both can vary without forcing constant rewrites. It keeps your code flexible when you anticipate multiple combinations of concepts and underlying mechanisms.
Intent
Split abstraction and implementation into separate class hierarchies that connect through composition.
Structure
The abstraction holds a reference to an implementor. The implementor defines low-level operations. Together they allow you to mix and match behaviours at runtime without creating a large number of subclasses.
Pseudocode
interface Device { method turn_on(); method turn_off() }
class TV implements Device { … }
class Radio implements Device { … }
class RemoteControl {
constructor(device)
method on() { device.turn_on() }
method off() { device.turn_off() }
}
The remote is the abstraction; the devices are implementations. Either side can grow independently.
Python
class Device:
def turn_on(self):
raise NotImplementedError
def turn_off(self):
raise NotImplementedError
class TV(Device):
def turn_on(self): return "TV on"
def turn_off(self): return "TV off"
class RemoteControl:
def __init__(self, device):
self.device = device
def on(self): return self.device.turn_on()
def off(self): return self.device.turn_off()
When to Use
Use Bridge when a hierarchy has two changing dimensions such as abstraction and platform or shape and rendering engine.
Tradeoffs
Bridge adds structural clarity at scale; for small hierarchies it may feel heavier than needed.
Composite
Composite lets you treat single objects and groups of objects in the same way. This makes tree structures easier to navigate and manipulate because leaves and containers share a common interface.
Intent
Represent part-whole hierarchies so clients can handle individual items and groups uniformly.
Structure
A component interface offers operations shared by both leaves and composites. The composite stores children and forwards operations to them, building large recursive structures with simple rules.
Pseudocode
interface Node { method show() }
class Leaf implements Node {
method show() { … }
}
class Group implements Node {
children = []
method add(n) { children.append(n) }
method show() {
for each child in children:
child.show()
}
}
This creates unified behaviour across simple and nested elements.
Python
class Leaf:
def __init__(self, name):
self.name = name
def show(self):
return self.name
class Group:
def __init__(self):
self.children = []
def add(self, node):
self.children.append(node)
def show(self):
return [child.show() for child in self.children]
When to Use
Use Composite when working with nested or hierarchical data such as file trees or UI elements.
Tradeoffs
Uniformity simplifies client code; enforcing a common interface may require compromise in component responsibilities.
Decorator
Decorator adds behaviour to objects by wrapping them in layers. Each decorator keeps the original interface while injecting new features around the existing operations.
Intent
Attach optional behaviour dynamically instead of using large inheritance chains.
Structure
Both component and decorator share the same interface. A decorator stores a component and calls it while adding its own pre or post logic.
Pseudocode
interface Writer { method write(x) }
class BasicWriter implements Writer { … }
class LoggingDecorator implements Writer {
constructor(inner)
method write(x) {
log("write called")
return inner.write(x)
}
}
Multiple decorators can wrap one another to build a chain of small enhancements.
Python
class Writer:
def write(self, x):
raise NotImplementedError
class BasicWriter(Writer):
def write(self, x):
return x
class LoggingDecorator(Writer):
def __init__(self, inner):
self.inner = inner
def write(self, x):
return f"log: {self.inner.write(x)}"
When to Use
Use Decorator when behaviour must be extended step by step without modifying original classes.
Tradeoffs
Decorators compose cleanly; however long chains can obscure control flow.
Facade
Facade provides a simplified gateway to a complex subsystem. It hides the inner maze behind a clean and unified interface.
Intent
Wrap many components behind one straightforward interface that clients can use without learning the entire subsystem.
Structure
The facade holds and coordinates subsystem objects. Clients send high-level requests to the facade; the facade delegates work to the appropriate parts.
Pseudocode
class Engine { method start() }
class Lights { method on() }
class CarFacade {
constructor(engine, lights)
method begin_trip() {
engine.start()
lights.on()
}
}
The client sees a simple operation while the facade handles the detailed sequence.
Python
class Engine:
def start(self): return "engine"
class Lights:
def on(self): return "lights"
class CarFacade:
def __init__(self, engine, lights):
self.engine = engine
self.lights = lights
def begin_trip(self):
return [self.engine.start(), self.lights.on()]
When to Use
Use Facade when a subsystem becomes too detailed for direct use or when you want clearer boundaries between layers.
Tradeoffs
Simplification can hide useful features; avoid making the facade the only entry unless that is the explicit goal.
Flyweight
Flyweight reduces memory usage by sharing common internal state across many similar objects. Only the small pieces of state that vary remain unique.
Intent
Share intrinsic data to support large numbers of fine-grained objects efficiently.
Structure
The factory returns shared flyweights for repeated intrinsic values. Extrinsic values are passed to operations at runtime, keeping each flyweight light.
Pseudocode
class Flyweight {
constructor(shared)
method render(extrinsic) { … }
}
class FlyweightFactory {
pool = {}
method get(shared):
if shared not in pool:
pool[shared] = new Flyweight(shared)
return pool[shared]
}
This keeps the number of created objects small even when the system uses many of them.
Python
class Flyweight:
def __init__(self, shared):
self.shared = shared
def render(self, extrinsic):
return f"{self.shared}:{extrinsic}"
class FlyweightFactory:
def __init__(self):
self.pool = {}
def get(self, shared):
if shared not in self.pool:
self.pool[shared] = Flyweight(shared)
return self.pool[shared]
When to Use
Use Flyweight when an application stores many small similar objects such as characters in a document or map tiles.
Tradeoffs
State must be split carefully; incorrect separation of intrinsic and extrinsic values creates bugs.
Proxy
Proxy controls access to another object. It behaves like a stand-in that forwards calls while adding checks or extra behaviour.
Intent
Provide a substitute that manages or protects access to a real object.
Structure
The proxy stores a reference to a subject. Each operation may add permission checks, caching or lazy creation before delegating to the real subject.
Pseudocode
interface Service { method request() }
class RealService implements Service { … }
class Proxy implements Service {
constructor(real)
method request() {
if allowed:
return real.request()
else:
return "blocked"
}
}
This allows the proxy to protect or manage use of the real service.
Python
class RealService:
def request(self):
return "real"
class Proxy:
def __init__(self, real):
self.real = real
def request(self):
return f"proxy:{self.real.request()}"
When to Use
Use Proxy for lazy loading, permission control, caching layers or network stubs that stand in for remote services.
Tradeoffs
Extra indirection can affect performance; thoughtful design keeps overhead small while preserving clarity.