Handbooks / OOP & Design Patterns / Chapter 4

Structural Patterns

7 pages · ~5 min✓ Reviewed

Builds on Creational Patterns. Next up: Behavioral Patterns.

OOP & Design PatternsPage 1

Structural Patterns Mental Model

1. One Skeleton

  • Compose classes and objects into bigger structures; behaviour contracts stay unchanged
  • Favour composition over inheritance
  • Wrapper holds a reference to the wrapped object and forwards calls
  • Patterns differ in intent, not in shape
  1. Client
  2. Wrapperholds a reference
  3. Wrappeddoes the work

2. Interface Decides the Pattern

Wrapper's interfacePatterns
ChangesAdapter, Facade
IdenticalDecorator, Proxy, Composite
Split abstraction from implementationBridge

3. One-Line Intents

PatternIntent
Adapterconverts one interface to another
Decoratoradds behaviour
Facadesimplifies a subsystem
Proxycontrols access
Compositetreats tree and leaf alike
Bridgedecouples abstraction from implementation
Quick test for look-alikes

Ask what the wrapper is for: convert, add, simplify, guard, aggregate. Not how it is built.

OOP & Design PatternsPage 2

Adapter and Facade: Changing the Interface

1. Adapter

  • Client expects Target; existing Adaptee has an incompatible interface
  • Adapter implements Target, translates each call to the Adaptee
  • Object adapter: holds adaptee by composition, preferred, works with subclasses
  • Class adapter: inherits adaptee via multiple inheritance, rare
typescript
class XmlToJsonAdapter implements JsonSource {
  constructor(private xml: XmlSource) {}
  read() { return toJson(this.xml.read()); }
}

object adapter: translates read()

2. Facade

  • One simplified entry point over a complex subsystem
  • VideoConverter.convert(file, 'mp4') hides codec, bitrate and audio classes
  • Subsystem classes stay publicly usable

3. Adapter vs Facade

AdapterFacade
GoalMatch an interface the client already expectsDefine a new, simpler interface
WrapsOne existing classMany classes
God object

A Facade that grows business logic becomes a god object. Keep it a thin delegator.

OOP & Design PatternsPage 3

Decorator and Proxy: Same Interface, Different Intent

1. Decorator: add behaviour

  • Wraps an object with the same interface, adds work before or after delegating
  • Decorators stack; the client picks the order
  • Java: new BufferedReader(new FileReader(f))
  • Python: @decorator adds logging or caching around a call
new Gzip(new Encrypt(new FileStream()))
  1. 1Gzipcompress
  2. 2Encryptthen encrypt
  3. 3FileStreamwrites

2. Proxy: control access

  • Stand-in with the same interface that guards the real object
  • Virtual: lazy-load the costly object
  • Protection: permission check
  • Remote: network stub
  • Caching: reuse earlier results

3. Decorator vs Proxy

DecoratorProxy
PurposeAdd featuresControl access
Chosen byClient stacks itOften invisible to client
Real objectPassed inProxy manages lifecycle

4. Mistakes

  • Order matters: compress then encrypt, since ciphertext barely compresses
  • Proxy that swallows errors or breaks the interface
  • Subclass per feature combination: class explosion, use decorators
Quick test

Ask what the wrapper is for: add behaviour is Decorator, guard access is Proxy.

OOP & Design PatternsPage 4

Composite and Bridge: Hierarchies and Abstractions

1. Composite: Tree as One Object

  • Component interface shared by Leaf and Composite
  • Composite holds child Components, forwards calls
  • Client treats one object and a tree alike
python
class File:
    def size(self): return self.bytes
class Folder:
    def size(self):
        return sum(c.size() for c in self.children)

Folder recurses; File is the base case

  • add(), remove() on Composite: safe
  • On Component: transparent, leaves fail
  • Trade-off: transparency vs safety

2. Bridge: Two Dimensions

  • Abstraction and Implementor hierarchies joined by composition
  • Shape with Renderer: Circle/Square x Vector/Raster
  • M+N classes, not M×N
  1. Circle, SquareAbstraction
  2. Shapeholds a Renderer
  3. Vector, RasterImplementor
BridgeAdapter
BuiltUp frontAfter the fact
GoalSplit two dimensions of changeMake incompatible classes fit

3. Mistakes

  • Bridge when only one dimension varies
  • Mixing up Strategy: it swaps an algorithm, Bridge a whole implementation hierarchy
OOP & Design PatternsPage 5

Choosing Between the Six Patterns

1. Pick by Intent

NeedPattern
Incompatible interfaceAdapter
Simplify a subsystemFacade
Add behaviour dynamicallyDecorator
Control access or lazy-loadProxy
Tree of uniform partsComposite
Two independent dimensions of variationBridge

2. In Real Code

ExamplePattern
Java BufferedReader over FileReaderDecorator
JS Proxy objectProxy
React component treeComposite
jQuery over the DOMFacade
ORM driver layerBridge
Node stream wrappersDecorator

3. Limits and Combos

  • One implementation only: skip the pattern
  • A plain function or direct call is enough: skip it
  • Facade may use Adapters inside
  • Composite tree may be wrapped by Decorators
  • Proxy can front a Facade
Quick test

Ask what the wrapper is for, not how it is built.

OOP & Design PatternsPage 6

Structural Patterns Cheat Sheet

1. Wrappers at a Glance

PatternIntentInterfaceExample
Adapterconvert A to B; wraps one objectchangeslegacy SDK wrapper
Facadesimplify a subsystemnew, simplerVideoConverter.convert()
Decoratoradd behaviour; stackablesameGzip(Encrypt(Stream))
Proxycontrol accesssamelazy image loader
  • Proxy kinds: virtual, protection, remote, cache

2. Composite and Bridge

CompositeBridge
Ideatree: leaf and group share one interfaceabstraction and implementation vary independently
Payoffclient treats one and many alikeM+N classes, not M×N
Quick test

Ask what the wrapper is for: convert, add, simplify, guard, aggregate, not how it is built.

OOP & Design PatternsPage 7

Check yourself

You call Gzip(Encrypt(FileStream())).write(data). In what order is the data transformed?
  • Compressed first, then encrypted: the outer decorator runs first, then delegates inward.
  • Swapping the order would compress ciphertext, which barely shrinks.
ReportProxy implements Report and wraps RealReport. Its load() catches every exception and returns an empty report. What is wrong?
  • It swallows errors, so callers can no longer tell failure from an empty result.
  • A proxy must keep the real object's contract, not hide its failures.
3 shapes (Circle, Square, Triangle) and 3 renderers (Vector, Raster, Svg). How many concrete classes with Bridge, and with plain subclassing?
  • Bridge: 3 + 3 = 6 concrete classes.
  • Subclassing: 3 × 3 = 9, and every new shape adds a whole row.