Architecture

Structural Design Patterns: Composing Objects with Purpose

Build elegant system architectures through composition—master Adapter, Bridge, Decorator, and Facade patterns that solve the complex challenge of connecting incompatible components into cohesive, maintainable systems

Series: Design Patterns and Analysis | Part 3 of 4 > Developed during Master’s in Web Systems Projects

Continuing our series, after exploring how to build objects with flexibility using Creational Patterns, it’s time to understand how to structure them for better collaboration. Structural Design Patterns focus on how classes and objects are combined to form larger structures — without unnecessary complexity or tight coupling.

What Are Structural Patterns?

Structural patterns describe ways to compose objects and classes into larger systems, ensuring that changes in one part do not ripple destructively through the codebase.

They help with:

  • Adapting interfaces that weren’t designed to work together
  • Adding responsibilities dynamically
  • Hiding internal complexity behind simpler interfaces

Types of Structural Patterns

Adapter

Converts the interface of a class into another expected by the client.

  • Intent: Bridge between incompatible interfaces.
  • Use When: You want to reuse an existing class but its interface doesn’t match.
UML Diagram of Adapter
class RoundHole {
    boolean fits(RoundPeg peg) { ... }
}

class SquarePegAdapter extends RoundPeg {
    private SquarePeg peg;
    double getRadius() { ... } // converts square to round logic
}

Bridge

Decouples an abstraction from its implementation so they can evolve independently.

  • Intent: Split logic into abstraction and implementation layers.
  • Use When: You want to vary both abstractions and implementations.
UML Diagram of Bridge
interface Device {
    void enable();
    void disable();
}

class Remote {
    protected Device device;
    public void togglePower() {
        if (device.isEnabled()) device.disable();
        else device.enable();
    }
}

Composite

Composes objects into tree structures and treats them uniformly.

  • Intent: Treat individual objects and compositions the same way.
  • Use When: You work with recursive structures like UI trees or file systems.
UML Diagram of Composite
interface Graphic {
    void draw();
}

class CompoundGraphic implements Graphic {
    private List<Graphic> children;
    void draw() {
        for (Graphic child : children) child.draw();
    }
}

Decorator

Adds responsibilities to objects dynamically.

  • Intent: Wrap an object to extend its behavior.
  • Use When: You want to avoid subclass explosion and keep things flexible.
UML Diagram of Decorator
interface DataSource {
    void writeData(String data);
}

class CompressionDecorator implements DataSource {
    private DataSource wrappee;
    void writeData(String data) {
        wrappee.writeData(compress(data));
    }
}

Facade

Provides a simplified interface to a complex subsystem.

  • Intent: Hide complexity and expose only what’s necessary.
  • Use When: You need a clean API over complex internals.
UML Diagram of Facade
class VideoConverter {
    public File convert(String filename, String format) {
        // interacts with many video-related classes internally
    }
}

Flyweight

Shares common parts of state across many objects to save memory.

  • Intent: Use sharing to support large numbers of fine-grained objects efficiently.
  • Use When: You need to manage many objects with similar data (e.g., game tiles, fonts).
UML Diagram of Flyweight
class TreeType {
    String texture;
    void draw(int x, int y) { ... }
}

Proxy

Acts as a placeholder for another object to control access, lazy load, or log.

  • Intent: Control access to an object.
  • Use When: You need extra logic around the real object without changing it.
UML Diagram of Proxy
class ImageProxy implements Image {
    private RealImage realImage;
    void display() {
        if (realImage == null) realImage = new RealImage();
        realImage.display();
    }
}

Comparison Table

PatternPurposeBest For
AdapterInterface conversionLegacy code, integration
BridgeSeparate abstraction from implementationUI frameworks, device control
CompositeRecursive tree structureGraphics, UIs, folders
DecoratorAdd behavior without subclassingI/O streams, logging
FacadeSimplify subsystem usageAPI gateways, libraries
FlyweightShare objects for memory efficiencyRendering engines, games
ProxyAccess controlVirtual proxies, protection, caching

Final Thoughts

Structural patterns help us compose systems with elegance, enabling adaptation, extension, and simplification of architecture without creating a domino effect of changes.

Next up, we’ll explore Behavioral Patterns — how to coordinate responsibilities and workflows flexibly.


Series Navigation