Command Palette

Search for a command to run...

Level 2 · 25 min

Structural Patterns

Structural patterns explain how to assemble objects and classes into larger structures while keeping those structures flexible and efficient. They use inheritance and composition to create new functionality.

Adapter and Bridge

Adapter converts the interface of a class into another interface that clients expect — makes incompatible classes work together. Example: wrapping a legacy XML API with a new JSON interface. Bridge decouples an abstraction from its implementation so both can vary independently — instead of inheriting, the abstraction holds a reference to the implementation. Avoids the M×N explosion of subclasses.

Composite and Decorator

Composite lets you compose objects into tree structures and treat individual objects and compositions uniformly — filesystem with files and directories. Decorator adds behavior to objects dynamically without subclassing — wraps the object and forwards calls with additional logic. InputStream → BufferedInputStream → GZIPInputStream is the canonical Java example. Unlike inheritance, decorators can be composed at runtime. GoF states Decorator's intent as: "Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality." (Gamma et al., Design Patterns, p.175). The GoF authors note a critical consequence: "More flexibility than static inheritance. With decorators, responsibilities can be added and removed at run-time simply by attaching and detaching them. In contrast, inheritance requires creating a new class for each additional combination." (p.172). In Spring Framework, @Transactional is implemented as a JDK dynamic proxy or CGLIB proxy that wraps the target bean — a Decorator that adds begin/commit/rollback around every method call. The business class never changes; the decorator is applied transparently by the container.

Facade and Proxy

Facade provides a simplified unified interface to a complex subsystem — hides the complexity of multiple APIs behind a single easy-to-use interface. Proxy is a surrogate that controls access to another object. Uses: Virtual Proxy (lazy loading — create expensive object only when needed), Protection Proxy (access control), Remote Proxy (local representative for remote object), Caching Proxy (cache results).

Key Takeaways

  • Adapter makes incompatible interfaces compatible. Bridge prevents M×N subclass explosion.
  • Decorator adds behavior at runtime via composition — more flexible than inheritance for cross-cutting concerns.
  • Proxy controls access — Virtual (lazy), Protection (auth), Caching (memoize), Remote (network).

Code example

// Decorator: logging around any service
interface OrderService { Order process(OrderRequest req); }

class OrderServiceImpl implements OrderService {
  public Order process(OrderRequest req) { /* real logic */ }
}

class LoggingOrderService implements OrderService {
  private final OrderService delegate;
  private final Logger log;

  LoggingOrderService(OrderService delegate, Logger log) {
    this.delegate = delegate; this.log = log;
  }

  public Order process(OrderRequest req) {
    log.info("Processing order {}", req.id());
    long start = System.currentTimeMillis();
    try {
      Order result = delegate.process(req);
      log.info("Order {} processed in {}ms", req.id(), System.currentTimeMillis() - start);
      return result;
    } catch (Exception e) {
      log.error("Order {} failed", req.id(), e);
      throw e;
    }
  }
}