Decorator Pattern
Attach responsibilities to an object dynamically by wrapping it — a flexible alternative to subclassing.
Intent
Decorator wraps an object in another object that has the same interface, adding behaviour before or after delegating to it. Because decorators are themselves components, they can be stacked in any order at runtime.
Why not subclassing?
To add logging, caching and retries to a DataSource via inheritance you need LoggingDataSource, CachingDataSource, LoggingCachingDataSource, RetryingLoggingCachingDataSource… — one class per combination. Decorators let you compose them instead:
DataSource ds = new Retrying(new Caching(new Logging(new HttpDataSource())));
Structure
classDiagram
class TextSource {
<<interface>>
+read() String
}
class FileSource {
+read() String
}
class SourceDecorator {
<<abstract>>
#TextSource inner
+read() String
}
class TrimDecorator
class UpperCaseDecorator
class CensorDecorator
TextSource <|.. FileSource
TextSource <|.. SourceDecorator
SourceDecorator o--> TextSource : wraps
SourceDecorator <|-- TrimDecorator
SourceDecorator <|-- UpperCaseDecorator
SourceDecorator <|-- CensorDecorator
Example: a coffee order, the classic way
import java.math.BigDecimal;
interface Beverage {
String description();
BigDecimal cost();
}
record Espresso() implements Beverage {
public String description() { return "Espresso"; }
public BigDecimal cost() { return new BigDecimal("2.00"); }
}
// Base decorator: delegates everything to the wrapped beverage
abstract class AddOn implements Beverage {
protected final Beverage inner;
AddOn(Beverage inner) { this.inner = inner; }
public String description() { return inner.description(); }
public BigDecimal cost() { return inner.cost(); }
}
class Milk extends AddOn {
Milk(Beverage b) { super(b); }
public String description() { return inner.description() + " + milk"; }
public BigDecimal cost() { return inner.cost().add(new BigDecimal("0.40")); }
}
class Caramel extends AddOn {
Caramel(Beverage b) { super(b); }
public String description() { return inner.description() + " + caramel"; }
public BigDecimal cost() { return inner.cost().add(new BigDecimal("0.55")); }
}
class Large extends AddOn {
Large(Beverage b) { super(b); }
public String description() { return "Large " + inner.description(); }
public BigDecimal cost() { return inner.cost().multiply(new BigDecimal("1.3")).setScale(2, java.math.RoundingMode.HALF_UP); }
}
class Main {
public static void main(String[] args) {
Beverage order = new Large(new Caramel(new Milk(new Milk(new Espresso()))));
System.out.println(order.description() + " = £" + order.cost());
// Order matters: size applied before vs after the extras
Beverage other = new Caramel(new Large(new Espresso()));
System.out.println(other.description() + " = £" + other.cost());
}
}Functional decorators
When the component interface is a single method, decorators can be plain functions. Function.andThen and compose are decorator stacking in disguise:
UnaryOperator<String> trim = String::strip;
UnaryOperator<String> upper = String::toUpperCase;
Function<String, String> pipeline = trim.andThen(upper).andThen(s -> s.replace("SECRET", "******"));
pipeline.apply(" the secret plan "); // "THE ****** PLAN"
Where you’ll see it
java.iois built on decorators:new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(f))))— each layer adds buffering, decoding or decompressionCollections.unmodifiableList(list),Collections.synchronizedMap(map),Collections.checkedList(...)- Spring:
TransactionAwareDataSourceProxy,ContentCachingRequestWrapper,HttpServletRequestWrapperused inside servlet filters - Spring Cloud / Resilience4j: decorating a
Supplierwith a circuit breaker, then a retry, then a rate limiter
Trade-offs
| Pros | Cons |
|---|---|
| Add or remove behaviour at runtime | Lots of small wrapper objects; debugging stack traces gets deep |
| Combine features freely — no class explosion | Order of wrapping matters and can surprise |
| Each decorator has a single responsibility | Identity checks (==) on the wrapped object break |
Mark it complete to track your progress through Design Patterns.