Attach additional responsibilities to an object dynamically — a flexible alternative to subclassing for extending functionality.
Decorator wraps an object inside another object that adds new behaviour before or after delegating to the wrapped object. Both the wrapper and the wrapped object implement the same interface — so the client can't tell the difference.
Decorators can be stacked: wrap a decorator inside another decorator to layer multiple behaviours. This is far more flexible than subclassing, which bakes behaviour in at compile time.
You have a TextEditor class. You want variants: one with spell-check, one with auto-save, one with both, one with syntax highlighting too. Using inheritance you'd need SpellCheckEditor, AutoSaveEditor, SpellCheckAutoSaveEditor... a combinatorial explosion.
Decorator lets you wrap the base editor in a SpellCheckDecorator, then optionally wrap that in an AutoSaveDecorator at runtime — combining freely without any new subclasses.
// Component interface public interface Coffee { String getDescription(); double getCost(); } // ConcreteComponent — base object public class Espresso implements Coffee { public String getDescription() { return "Espresso"; } public double getCost() { return 1.00; } } public class SimpleCoffee implements Coffee { public String getDescription() { return "Simple Coffee"; } public double getCost() { return 0.75; } } // Abstract Decorator — wraps a Coffee, delegates all calls public abstract class CoffeeDecorator implements Coffee { protected final Coffee wrapped; // THE WRAPPED COMPONENT public CoffeeDecorator(Coffee coffee) { this.wrapped = coffee; } // Default: delegate to wrapped (subclasses override to add behaviour) public String getDescription() { return wrapped.getDescription(); } public double getCost() { return wrapped.getCost(); } } // Concrete Decorators — add specific behaviours public class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); } public String getDescription() { return wrapped.getDescription() + ", Milk"; // add after } public double getCost() { return wrapped.getCost() + 0.25; } } public class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); } public String getDescription() { return wrapped.getDescription() + ", Sugar"; } public double getCost() { return wrapped.getCost() + 0.10; } } public class VanillaDecorator extends CoffeeDecorator { public VanillaDecorator(Coffee coffee) { super(coffee); } public String getDescription() { return wrapped.getDescription() + ", Vanilla"; } public double getCost() { return wrapped.getCost() + 0.50; } } // Client — stacks decorators freely at runtime public class CoffeeShop { public static void main(String[] args) { // Plain espresso Coffee order = new Espresso(); System.out.println(order.getDescription() + " $" + order.getCost()); // Espresso $1.00 // Add milk order = new MilkDecorator(order); // Add sugar order = new SugarDecorator(order); // Add vanilla order = new VanillaDecorator(order); System.out.println(order.getDescription() + " $" + order.getCost()); // Espresso, Milk, Sugar, Vanilla $1.85 // Same base, different combination — no new subclass needed Coffee plain = new SugarDecorator(new SimpleCoffee()); System.out.println(plain.getDescription() + " $" + plain.getCost()); // Simple Coffee, Sugar $0.85 } }
java.io.BufferedInputStream(new FileInputStream(...)) — textbook Decorator stackingjava.io.DataOutputStream(new BufferedOutputStream(new FileOutputStream(...))) — 3-layer decorator chainSecurityFilter wraps the next, adding auth/authz behaviourHttpServletRequestWrapper — decorates request to add custom behaviour@Delegate — generates delegation boilerplate used in decorator-style wrapperswrapped.method() — silently drops the base behaviour and all inner decoratorsobj instanceof Espresso returns false when wrapped — code relying on type identity breaks silentlyCoffeeDecorator default implementation delegates to wrapped — subclasses call super.method() to stay in the chainwrapped.method() live — never read a field from the wrapped object and store itinstanceof checks on the decorated typecoffee == this to prevent infinite recursion// ❌ BREAKING — forgetting to delegate public class BrokenMilkDecorator extends CoffeeDecorator { public BrokenMilkDecorator(Coffee c) { super(c); } public double getCost() { return 0.25; // ❌ forgot wrapped.getCost() — base cost lost! } } // ✅ FIX — always delegate to wrapped public double getCost() { return wrapped.getCost() + 0.25; // ✅ chain preserved } // ❌ BREAKING — null wrapped object Coffee bad = new MilkDecorator(null); // NPE on getCost() // ✅ FIX — validate in constructor public CoffeeDecorator(Coffee coffee) { if (coffee == null) throw new IllegalArgumentException("Wrapped coffee cannot be null"); this.wrapped = coffee; } // ❌ BREAKING — identity check fails after decorating Coffee order = new MilkDecorator(new Espresso()); if (order instanceof Espresso) { // false! now MilkDecorator // logic never runs — silent bug } // ✅ FIX — use interface methods, never instanceof on wrapped type // Add a method to the Coffee interface if you need to query type public interface Coffee { String getDescription(); double getCost(); boolean isEspressoBase(); // let each type decide }
wrapped Component field. ConcreteDecorators call wrapped.method() and add before/after. Stack by passing one into another's constructor.