Structural Design Patterns
Structural patterns compose classes and objects to form larger structures while keeping them flexible and efficient.
Adapter
Convert the interface of one type to another that clients expect. Makes incompatible types work together.
// Existing interface
INTERFACE MediaPlayer
PROCEDURE PLAY(filename)
// Incompatible third-party library
CLASS VlcLib
PROCEDURE PLAY_VLC(file) PRINT "VLC playing: ", file
// Adapter wraps VlcLib to implement MediaPlayer
CLASS VlcAdapter IMPLEMENTS MediaPlayer
FIELDS: vlc (VlcLib)
PROCEDURE PLAY(filename)
self.vlc.PLAY_VLC(filename) // delegate to adapted interface
When to use: Integrating third-party libraries. Legacy code integration. Converting between incompatible interfaces.
Rust idiom: From/Into traits are Rust's built-in adapter mechanism.
Bridge
Separate an abstraction from its implementation so they can vary independently.
// Implementation hierarchy (how to render)
INTERFACE Renderer
PROCEDURE RENDER_CIRCLE(x, y, radius)
PROCEDURE RENDER_RECT(x, y, w, h)
CLASS SvgRenderer IMPLEMENTS Renderer
PROCEDURE RENDER_CIRCLE(x, y, r) PRINT "<circle cx=", x, " cy=", y, " r=", r, "/>"
PROCEDURE RENDER_RECT(x, y, w, h) PRINT "<rect .../>"
CLASS CanvasRenderer IMPLEMENTS Renderer
PROCEDURE RENDER_CIRCLE(x, y, r) PRINT "ctx.arc(", x, ",", y, ",", r, ",0,2*PI)"
PROCEDURE RENDER_RECT(x, y, w, h) PRINT "ctx.fillRect(", x, ",", y, ",", w, ",", h, ")"
// Abstraction hierarchy (what to draw)
CLASS Circle
FIELDS: x, y, radius, renderer (Renderer)
PROCEDURE DRAW()
self.renderer.RENDER_CIRCLE(self.x, self.y, self.radius)
When to use: When both the abstraction and implementation should be extendable independently. Platform-specific implementations. Driver/hardware abstraction.
Composite
Compose objects into tree structures to represent part-whole hierarchies. Treat individual objects and compositions uniformly.
INTERFACE Component
FUNCTION SIZE() -> integer
FUNCTION NAME() -> string
CLASS File IMPLEMENTS Component
FIELDS: name, size
FUNCTION SIZE() RETURN self.size
FUNCTION NAME() RETURN self.name
CLASS Directory IMPLEMENTS Component
FIELDS: name, children (list of Component)
FUNCTION SIZE() RETURN SUM(c.SIZE() FOR EACH c IN self.children)
FUNCTION NAME() RETURN self.name
When to use: Tree structures (file systems, UI component trees, organization hierarchies, ASTs). When clients should treat leaves and composites uniformly.
Decorator
Attach additional responsibilities to an object dynamically. Alternative to subclassing for extending functionality.
INTERFACE DataSource
PROCEDURE WRITE(data)
FUNCTION READ() -> bytes
CLASS FileDataSource IMPLEMENTS DataSource
PROCEDURE WRITE(data) self.data ← data
FUNCTION READ() RETURN self.data
// Decorator: adds compression
CLASS CompressionDecorator IMPLEMENTS DataSource
FIELDS: wrapped (DataSource)
PROCEDURE WRITE(data)
compressed ← COMPRESS(data)
self.wrapped.WRITE(compressed)
FUNCTION READ()
data ← self.wrapped.READ()
RETURN DECOMPRESS(data)
// Decorator: adds encryption
CLASS EncryptionDecorator IMPLEMENTS DataSource
FIELDS: wrapped (DataSource)
// ... similar wrapping with encrypt/decrypt
// Stack decorators:
source ← new FileDataSource
compressed ← new CompressionDecorator(wrapped: source)
encrypted ← new EncryptionDecorator(wrapped: compressed)
// encrypted -> compresses -> then encrypts -> then writes to file
When to use: Add behavior at runtime without modifying the original. Combine behaviors flexibly. I/O streams (buffered, compressed, encrypted), logging, caching.
Rust idiom: BufReader/BufWriter are decorators wrapping Read/Write.
Facade
Provide a simplified interface to a complex subsystem.
// Complex subsystem
CLASS VideoDecoder, AudioDecoder, SubtitleRenderer, DisplayEngine
// Facade: simple interface
CLASS MediaPlayer
FIELDS: video, audio, subs, display
PROCEDURE PLAY(filename)
video ← self.video.DECODE(filename)
audio ← self.audio.DECODE(filename)
subs ← self.subs.LOAD(filename)
self.display.RENDER(video, audio, subs)
// Client just calls player.PLAY("movie.mp4")
When to use: Simplify a complex API. Provide a higher-level interface. Decouple clients from subsystem internals.
Flyweight
Share state between many similar objects to reduce memory usage.
// Shared (intrinsic) state — flyweight
CLASS TreeType
FIELDS: name, texture (large data, shared)
// Unique (extrinsic) state — per instance
CLASS Tree
FIELDS: x, y, tree_type (index into flyweight pool)
CLASS TreeFactory
FIELDS: types (list), type_map (dictionary)
FUNCTION GET_TYPE(name)
IF name IN type_map
RETURN type_map[name] // reuse existing
ELSE
idx ← length(types)
APPEND new TreeType(name, LOAD_TEXTURE(name)) TO types
type_map[name] ← idx
RETURN idx
// 1 million trees but only ~10 tree types (each with a large texture)
When to use: Many similar objects consuming too much memory. Significant shared state. Game objects (particles, tiles), text rendering (glyphs), caching.
Rust idiom: Interning (String::intern), Rc<T> / Arc<T> for shared ownership.
Proxy
Provide a substitute or placeholder for another object to control access.
INTERFACE Image
PROCEDURE DISPLAY()
// Real image — expensive to load
CLASS HighResImage IMPLEMENTS Image
FIELDS: data (bytes)
STATIC FUNCTION LOAD(path)
PRINT "Loading large image from disk..."
RETURN new HighResImage(data: READ_FILE(path))
PROCEDURE DISPLAY()
PRINT "Displaying image (", length(self.data), " bytes)"
// Proxy — defers loading until needed
CLASS LazyImage IMPLEMENTS Image
FIELDS: path, image (lazy)
PROCEDURE DISPLAY()
IF image is not initialized
image ← HighResImage.LOAD(self.path)
image.DISPLAY()
Proxy types:
- Virtual proxy: Lazy initialization (load on first use).
- Protection proxy: Access control (check permissions before delegating).
- Remote proxy: Represent a remote object locally (RPC, gRPC stubs).
- Caching proxy: Cache results of expensive operations.
- Logging proxy: Log all method calls.
Module Pattern
Group related functionality into a module with a public API and private implementation details.
MODULE payments
// Private implementation
PRIVATE CLASS StripeClient
// Public API
PUBLIC CLASS PaymentService
PUBLIC FUNCTION PROCESS_PAYMENT(amount, card)
client ← new StripeClient()
RETURN client.CHARGE(amount, card)
// Usage: payments.PROCESS_PAYMENT(1000, "tok_xxx")
Rust's module system is the primary mechanism for structural organization.
Applications in CS
- I/O libraries: Decorator pattern for streams (BufReader, GzDecoder, TlsStream).
- GUI frameworks: Composite pattern for widget trees. Decorator for adding behaviors (scrollbars, borders).
- Web frameworks: Middleware is the decorator pattern. Facade for complex subsystems.
- Game engines: Flyweight for particles and tiles. Proxy for asset loading.
- APIs: Adapter for integrating external services. Facade for simplifying complex APIs.
- Caching: Proxy pattern for transparent caching layers.