Start Learning
Javaneer
Back to stage
Stage 5·Software Craftsmanship

Design Patterns - Structural

Adapter, Decorator, Facade, Proxy, Composite, Bridge, and Flyweight.

22 min readAdvanced
On this page

Structural patterns deal with how classes and objects are composed into larger structures - while keeping those structures flexible and efficient. They're about assembling parts elegantly.

Adapter - make incompatible interfaces work together

Wraps one interface so it looks like another the client expects. Perfect for fitting a third-party library or legacy code into your system.

An Adapter is like a travel plug adapter

Your laptop charger doesn't fit a foreign socket. You don't rewire the charger or the wall - you slot in an adapter that translates between them. In code, an adapter translates one interface into another so incompatible parts cooperate.

// Your app expects this
interface PaymentProcessor { void pay(int cents); }

// A third-party library has a different shape
class StripeApi { void makeCharge(double dollars) { } }

// Adapter bridges them
class StripeAdapter implements PaymentProcessor {
    private final StripeApi stripe = new StripeApi();
    public void pay(int cents) {
        stripe.makeCharge(cents / 100.0);   // translate the call
    }
}

Decorator - add behavior by wrapping

Adds responsibilities to an object dynamically by wrapping it, without changing its class. Wrappers can stack.

A Decorator is like dressing for the weather

You start with a t-shirt, add a sweater, then a raincoat. Each layer adds warmth or protection without changing the layers beneath. Decorators wrap an object in layers, each adding behavior, all still looking like the original.

interface Coffee { double cost(); }
class Espresso implements Coffee { public double cost() { return 2.0; } }

class MilkDecorator implements Coffee {
    private final Coffee inner;
    MilkDecorator(Coffee inner) { this.inner = inner; }
    public double cost() { return inner.cost() + 0.5; }   // wraps + adds
}

Coffee order = new MilkDecorator(new MilkDecorator(new Espresso()));
System.out.println(order.cost());   // 3.0

Java's own I/O streams use this: new BufferedReader(new FileReader(...)).

Facade - a simple front for a complex system

Provides one simplified interface to a complicated subsystem, hiding its messy details.

// A tangle of subsystems...
class HomeTheaterFacade {
    void watchMovie() {
        projector.on();
        screen.down();
        amplifier.setSurround();
        lights.dim();
        player.play();
    }
}
theater.watchMovie();   // one call instead of five
A Facade is like a restaurant menu

You order 'the burger meal' - one simple request. Behind the kitchen doors, the grill, fryer, and prep stations coordinate a dozen steps. The menu is a facade: a clean front hiding a complex system.

Proxy - a stand-in that controls access

A proxy is a placeholder that controls access to another object - for lazy loading, caching, access control, or logging.

interface Image { void display(); }

class RealImage implements Image {
    RealImage(String file) { /* expensive: load from disk */ }
    public void display() { }
}

class LazyImage implements Image {         // proxy
    private final String file;
    private RealImage real;
    LazyImage(String file) { this.file = file; }
    public void display() {
        if (real == null) real = new RealImage(file);  // load only when needed
        real.display();
    }
}

Proxies power frameworks

Spring uses proxies behind the scenes: when you annotate a method as @Transactional or @Cacheable, Spring wraps your object in a proxy that adds that behavior around your method - without you writing the plumbing.

Composite - treat groups and individuals the same

Lets you treat individual objects and compositions of objects uniformly - perfect for tree structures like files/folders or UI components.

interface FileSystemItem { int size(); }

record File(int size) implements FileSystemItem {
    public int size() { return size; }
}
class Folder implements FileSystemItem {
    private final List<FileSystemItem> children = new ArrayList<>();
    void add(FileSystemItem item) { children.add(item); }
    public int size() {                              // sums files AND subfolders
        return children.stream().mapToInt(FileSystemItem::size).sum();
    }
}

A folder and a file both answer size() - callers don't care which they hold.

Bridge & Flyweight (briefly)

  • Bridge - separates an abstraction from its implementation so they vary independently (e.g. Shape and Renderer evolving separately).
  • Flyweight - shares common state across many objects to save memory (e.g. reusing one glyph object for every 'a' in a document).

Quick check

You want to add logging and caching to an object's method without changing its class, controlling access to it. Which pattern?

Key takeaways

  • Structural patterns compose classes and objects into larger, flexible structures.
  • Adapter translates one interface into another so incompatible parts work together.
  • Decorator wraps an object in layers, each adding behavior (like Java I/O streams).
  • Facade provides one simple front over a complex subsystem.
  • Proxy is a stand-in that controls access - powering framework features like @Transactional.
  • Composite lets you treat individual objects and groups uniformly (trees like files/folders).

Finally in patterns: behavioral patterns, which govern how objects communicate and share responsibility.