🚀AdvancedDesign Patterns

Structural Design Patterns

Implement Adapter, Decorator, Proxy, and Composite patterns to compose objects and classes.

Overview

Structural patterns describe how to compose objects and classes into larger structures while keeping those structures flexible and efficient. The four most important structural patterns are: - Adapter: converts one interface to another that clients expect - Decorator: adds responsibilities to objects dynamically without subclassing - Proxy: provides a placeholder or surrogate to control access to another object - Composite: composes objects into tree structures to represent part-whole hierarchies All four patterns wrap an object, but for different reasons: Adapter changes the interface, Decorator adds behavior, Proxy controls access, and Composite enables tree structures.

Key Points

  • •Adapter wraps an incompatible class to match a required interface
  • •Object Adapter uses composition; Class Adapter uses multiple inheritance (not possible in Java, so object adapter is the standard)
  • •Decorator implements the same interface as the decorated object and delegates + adds behavior
  • •Decorators can be stacked: new BoldDecorator(new ItalicDecorator(text))
  • •Java I/O streams use Decorator heavily: new BufferedReader(new FileReader(path))
  • •Proxy implements the same interface and intercepts calls for logging, security, caching, or lazy loading
  • •Java dynamic proxies (java.lang.reflect.Proxy) create proxies at runtime
  • •Composite allows treating leaf and composite nodes uniformly through a common interface

Examples

Adapter: plugging a legacy library into a new interface

// New interface the rest of the application uses
interface PaymentProcessor {
    void processPayment(double amount);
}

// Legacy library with incompatible API
class LegacyPaymentGateway {
    public void executeTransaction(String amount, String currency) {
        System.out.println("Legacy gateway: " + amount + " " + currency);
    }
}

// Adapter wraps the legacy class and exposes the new interface
class PaymentGatewayAdapter implements PaymentProcessor {
    private final LegacyPaymentGateway legacy;

    PaymentGatewayAdapter(LegacyPaymentGateway legacy) {
        this.legacy = legacy;
    }

    @Override
    public void processPayment(double amount) {
        legacy.executeTransaction(String.valueOf(amount), "USD");
    }
}

public class Main {
    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentGatewayAdapter(new LegacyPaymentGateway());
        processor.processPayment(99.99); // calls legacy internally
    }
}

The client (Main) uses PaymentProcessor and never knows about LegacyPaymentGateway. Swapping to a different gateway only requires a different Adapter.

Decorator: stacking text formatting behaviors

interface TextFormatter {
    String format(String text);
}

class PlainText implements TextFormatter {
    @Override
    public String format(String text) { return text; }
}

abstract class TextDecorator implements TextFormatter {
    protected final TextFormatter inner;
    TextDecorator(TextFormatter inner) { this.inner = inner; }
}

class BoldDecorator extends TextDecorator {
    BoldDecorator(TextFormatter inner) { super(inner); }
    @Override
    public String format(String text) {
        return "**" + inner.format(text) + "**";
    }
}

class UpperCaseDecorator extends TextDecorator {
    UpperCaseDecorator(TextFormatter inner) { super(inner); }
    @Override
    public String format(String text) {
        return inner.format(text).toUpperCase();
    }
}

public class Main {
    public static void main(String[] args) {
        TextFormatter f = new BoldDecorator(new UpperCaseDecorator(new PlainText()));
        System.out.println(f.format("hello")); // **HELLO**
    }
}

Decorators are composed by nesting constructors. Each layer adds one responsibility. Adding a new formatting behavior requires only a new decorator class, not changes to existing classes.

Proxy: caching proxy for an expensive operation

interface DataService {
    String fetchData(String key);
}

class RealDataService implements DataService {
    @Override
    public String fetchData(String key) {
        System.out.println("Fetching from database: " + key);
        return "data-for-" + key; // simulates expensive DB call
    }
}

class CachingProxy implements DataService {
    private final DataService real = new RealDataService();
    private final java.util.Map<String, String> cache = new java.util.HashMap<>();

    @Override
    public String fetchData(String key) {
        return cache.computeIfAbsent(key, real::fetchData);
    }
}

public class Main {
    public static void main(String[] args) {
        DataService service = new CachingProxy();
        System.out.println(service.fetchData("user:1")); // fetches from DB
        System.out.println(service.fetchData("user:1")); // returns from cache
        System.out.println(service.fetchData("user:2")); // fetches from DB
    }
}

The client uses DataService and does not know about caching. The proxy transparently intercepts calls and serves cached results when available.

Common Mistakes

  • Confusing Adapter with Decorator. Adapter changes the interface. Decorator keeps the same interface but adds behavior. If the interface before and after wrapping is the same, it is Decorator.
  • Confusing Proxy with Decorator. Proxy controls access to the same interface (caching, security, remote). Decorator adds behavior to the same interface. The distinction is intent, not structure.
  • Creating too many decorator layers. Each layer adds a method call. For performance-critical paths, consider combining decorators into a single class.

Note

Java I/O is the textbook example of Decorator: InputStream, FileInputStream, BufferedInputStream, DataInputStream all implement or extend the same base. You can wrap them in any order to compose behavior: new DataInputStream(new BufferedInputStream(new FileInputStream("file.dat"))).

Exercises

Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.

Loading editor…

Completed: 0 / 2 exercises

Knowledge Check

Question 1 of 4

What is the key difference between Adapter and Decorator?