Loslegen
Javaneer
Zurück zur Stufe
Stufe 5·Software-Handwerk

Design Patterns - Structural

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

22 Min. LesezeitExperte

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

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.