Decorator Pattern
IntermediateDecorator wraps an object to add behaviour dynamically, composing features at runtime without modifying the original class.
Overview
The Decorator pattern attaches additional responsibilities to an object at runtime by wrapping it. Decorators implement the same interface as the wrapped object, so they can be stacked in any combination. Java's I/O streams (BufferedInputStream, GZIPOutputStream, DataInputStream) are the classic Decorator example in the JDK. Decorators are a flexible alternative to subclassing for extending behaviour.
Classic Decorator
Define a component interface. The ConcreteComponent provides the base implementation. Decorators extend an abstract Decorator class that holds a reference to a Component and delegates to it, adding behaviour before or after the delegation.
Decorators can be stacked — each wraps the one beneath it, adding a layer of behaviour.
// Component interface
public interface Coffee {
String getDescription();
double getCost();
}
// Concrete component
public class SimpleCoffee implements Coffee {
@Override public String getDescription() { return "Coffee"; }
@Override public double getCost() { return 1.00; }
}
// Abstract decorator
public abstract class CoffeeDecorator implements Coffee {
protected final Coffee wrapped;
public CoffeeDecorator(Coffee coffee) { this.wrapped = coffee; }
@Override public String getDescription() { return wrapped.getDescription(); }
@Override public double getCost() { return wrapped.getCost(); }
}
// Concrete decorators
public class Milk extends CoffeeDecorator {
public Milk(Coffee c) { super(c); }
@Override public String getDescription() { return wrapped.getDescription() + ", Milk"; }
@Override public double getCost() { return wrapped.getCost() + 0.25; }
}
public class Vanilla extends CoffeeDecorator {
public Vanilla(Coffee c) { super(c); }
@Override public String getDescription() { return wrapped.getDescription() + ", Vanilla"; }
@Override public double getCost() { return wrapped.getCost() + 0.50; }
}
// Stack decorators at runtime
Coffee order = new Vanilla(new Milk(new Milk(new SimpleCoffee())));
System.out.println(order.getDescription()); // Coffee, Milk, Milk, Vanilla
System.out.println(order.getCost()); // 2.00Java I/O Streams as Decorators
Java's InputStream hierarchy is the most famous Decorator in the JDK. FileInputStream provides raw bytes; BufferedInputStream adds buffering; GZIPInputStream adds decompression; DataInputStream adds typed reads — all composable.
This design allows any combination of features without a class explosion from subclassing.
// Layered I/O decorators
try (InputStream raw = new FileInputStream("data.gz");
InputStream zip = new GZIPInputStream(raw);
InputStream buf = new BufferedInputStream(zip);
DataInputStream data = new DataInputStream(buf)) {
int count = data.readInt();
for (int i = 0; i < count; i++) {
System.out.println(data.readUTF());
}
}
// Writer decorators — same principle
try (Writer fw = new FileWriter("out.txt");
Writer bw = new BufferedWriter(fw);
PrintWriter pw = new PrintWriter(bw)) {
pw.println("Hello, Decorator!");
}Decorator with Functional Interfaces
With functional interfaces, decorators can be implemented as higher-order functions — functions that wrap other functions. This is the functional programming equivalent of the Decorator pattern.
import java.util.function.*;
// Decorator as function wrapper
Function<String, String> trim = String::trim;
Function<String, String> uppercase = String::toUpperCase;
Function<String, String> addBrackets = s -> "[" + s + "]";
// Compose decorators with andThen / compose
Function<String, String> pipeline =
trim.andThen(uppercase).andThen(addBrackets);
System.out.println(pipeline.apply(" hello "));
// [HELLO]
// Logging decorator for any Function
static <T, R> Function<T, R> withLogging(
Function<T, R> fn, String name) {
return input -> {
System.out.println("Calling " + name + " with: " + input);
R result = fn.apply(input);
System.out.println("Result: " + result);
return result;
};
}
Function<Integer, Integer> doubler = withLogging(n -> n * 2, "doubler");
doubler.apply(5); // logs call and resultKey Points to Remember
- Decorator wraps a component implementing the same interface, adding behaviour via delegation.
- Decorators can be stacked in any combination — this is more flexible than inheritance.
- Java I/O streams (BufferedInputStream, GZIPInputStream, DataInputStream) are the classic JDK example.
- Functional Decorator = higher-order function that wraps another function.
- The key difference from inheritance: Decorator adds behaviour at runtime, not compile time.
Practice Decorator Pattern in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaHow does Java's I/O stream hierarchy demonstrate the Decorator pattern?
What is the difference between Decorator and inheritance for extending behaviour?
What is the difference between Decorator and Proxy patterns?
How would you implement a caching decorator for a database repository?
Can you stack multiple Decorators? What are the implications?
Ask Aria about Decorator Pattern
Your personal AI tutor — ask anything about this concept