Decorator

Add Behaviour by Wrapping

A Decorator implements the same interface as the object it wraps and adds behaviour before/after delegating. Decorators can be stacked in any order — no explosion of subclasses like LoggingCachingRetryingClient.

Example

import java.util.*;

public class Main {
    public static void main(String[] args) {
        PriceService service = new LoggingPriceService(new CachingPriceService(new DbPriceService()));
        System.out.println(service.price("PHN-01"));
        System.out.println(service.price("PHN-01"));
        System.out.println(service.price("LAP-02"));
    }
}

interface PriceService { int price(String sku); }

class DbPriceService implements PriceService {
    public int price(String sku) {
        System.out.println("    DB query for " + sku);
        return sku.startsWith("PHN") ? 15999 : 54999;
    }
}

class CachingPriceService implements PriceService {
    private final PriceService inner;
    private final Map<String, Integer> cache = new HashMap<>();
    CachingPriceService(PriceService inner) { this.inner = inner; }
    public int price(String sku) { return cache.computeIfAbsent(sku, inner::price); }
}

class LoggingPriceService implements PriceService {
    private final PriceService inner;
    LoggingPriceService(PriceService inner) { this.inner = inner; }
    public int price(String sku) {
        System.out.println("-> price(" + sku + ")");
        return inner.price(sku);
    }
}

Output

-> price(PHN-01)
    DB query for PHN-01
15999
-> price(PHN-01)
15999
-> price(LAP-02)
    DB query for LAP-02
54999

In the JDK

java.io is built on decorators: new BufferedReader(new InputStreamReader(new FileInputStream(f))). Also Collections.unmodifiableList(list) and synchronizedMap(map). Spring AOP proxies (@Transactional, @Cacheable) apply the same idea automatically.