Structural Composition: The Decorator Pattern

The Decorator Pattern allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class. It is the architectural foundation for Middleware, Interceptors, and Resilient Clients.

I. Stacking Order Matters

In a decorator stack, the order of wrapping determines the execution flow.

II. Concrete Example: The Resilient HTTP Client

By composing simple decorators, we can build a production-grade HTTP client with multiple cross-cutting concerns.

interface HttpClient { String get(String url); }

class BaseClient implements HttpClient {
    @Override public String get(String url) { /* actual I/O */ return ""; }
}

class LoggingDecorator implements HttpClient {
    private final HttpClient inner;
    public LoggingDecorator(HttpClient inner) { this.inner = inner; }

    @Override public String get(String url) {
        System.out.println("Calling: " + url);
        return inner.get(url);
    }
}

class RetryDecorator implements HttpClient {
    private final HttpClient inner;
    @Override public String get(String url) {
        return retry(() -> inner.get(url)); // Logic to retry on 5xx
    }
}

// Composition: Resilient Client
HttpClient client = new LoggingDecorator(new RetryDecorator(new BaseClient()));

III. Transparency and the "Identity" Problem

A perfect decorator is Transparent: the client should not know it is talking to a wrapper.

IV. Dynamic Proxies (JDK Proxy)

For cross-cutting concerns that apply to many interfaces (e.g., generic metrics), use java.lang.reflect.Proxy. This avoids creating a manual decorator class for every interface in your system.

HttpClient proxy = (HttpClient) Proxy.newProxyInstance(
    HttpClient.class.getClassLoader(),
    new Class<?>[]{HttpClient.class},
    (p, method, args) -> {
        // Intercept logic here
        return method.invoke(realObject, args);
    }
);

See Also: