2 min read
On this page

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.