Design Pattern Course
Decorator is a Structural Design Pattern. It is also known as Wrapper.
Decorator attaches additional responsibilities to an object dynamically, at runtime, without modifying the original class or using subclassing. It achieves this through composition — a decorator wraps the original object, implements the same interface, and adds behaviour before or after it delegates to the wrapped object.
This is the key distinction from plain inheritance: with inheritance you bake new behaviour in at compile time and it applies to every instance of the subclass; with Decorator you stack behaviours onto an individual object at runtime, in any combination. The coffee-shop example below is the canonical illustration — any number of condiment decorators (Milk, Mocha, Sugar, Whip) can be layered onto any base drink in any order, without needing a separate subclass for every possible combination.
public interface MenuItem { double cost(); String description(); }
// Abstract decorator — wraps any MenuItem and delegates by default public abstract class Decorator implements MenuItem { protected final MenuItem beverage; public Decorator(MenuItem beverage) { this.beverage = beverage; } @Override public double cost() { return beverage.cost(); } @Override public String description() { return beverage.description(); } }
// Concrete leaf (the thing being decorated) public class HotChocolate implements MenuItem { @Override public double cost() { return 20.5; } @Override public String description() { return "Hot Chocolate"; } }
public class Espresso implements MenuItem { @Override public double cost() { return 18.0; } @Override public String description() { return "Espresso roast"; } }
// Concrete decorators public class Milk extends Decorator { public Milk(MenuItem beverage) { super(beverage); } @Override public double cost() { return beverage.cost() + 7.4; } @Override public String description() { return beverage.description() + ", Milk"; } }
public class Mocha extends Decorator { public Mocha(MenuItem beverage) { super(beverage); } @Override public double cost() { return beverage.cost() + 5.0; } @Override public String description() { return beverage.description() + ", Mocha"; } }
public class Main { public static void main(String[] args) { // Order a Hot Chocolate with Mocha MenuItem beverage = new HotChocolate(); beverage = new Mocha(beverage); System.out.println("Your Order: " + beverage.description()); System.out.println("Total Cost: " + beverage.cost()); // Stacking decorators: Espresso with Milk and Mocha MenuItem beverage2 = new Espresso(); beverage2 = new Milk(beverage2); beverage2 = new Mocha(beverage2); System.out.println("\nYour Order: " + beverage2.description()); System.out.println("Total Cost: " + beverage2.cost()); } }
Output
Your Order: Hot Chocolate, Mocha
Total Cost: 25.5
Your Order: Espresso roast, Milk, Mocha
Total Cost: 30.4
The Decorator base class was changed from class Decorator implements MenuItem with package-private state to an abstract class with a protected final field — this is the more standard and correct form. Making it abstract prevents anyone from instantiating a bare Decorator (which has no meaningful behaviour of its own), and making the field final makes it clear the wrapped delegate is set at construction time and not re-assigned.
Limitations
- Each added feature requires a new decorator class — for many features this can produce a large number of small classes.
- More objects in the chain means more complexity and potential for stack depth when calls cascade deeply.
- Debugging a chain of decorators can be harder than debugging a flat class hierarchy, since a wrapped object's
toString()or identity is often not obvious from the outside.
Decorator Pattern Examples in the JDK
java.iostreams — the most famous real-world use of Decorator in Java. All subclasses ofInputStream,OutputStream,Reader, andWriterthat take an instance of the same type in their constructor are decorators:BufferedInputStream(InputStream),DataInputStream(InputStream),GZIPOutputStream(OutputStream),PrintWriter(Writer), etc.java.util.Collections—synchronizedXXX(),unmodifiableXXX(), andcheckedXXX()factory methods all wrap an existing collection with a decorator that adds thread-safety, immutability enforcement, or runtime type-checking, respectively.javax.servlet.http.HttpServletRequestWrapperandHttpServletResponseWrapper— let you decorate an existing servlet request/response to override specific methods.javax.swing.JScrollPane— wraps a scrollable component to add scrollbar behaviour.