Decorator Pattern
Decorator Pattern
The Decorator pattern lets you attach new behaviors to an object at runtime by wrapping it inside another object that shares the same interface. Instead of creating a combinatorial explosion of subclasses, you compose small, focused decorators in any order you need.
Why Not Just Subclass?
Suppose you have a TextEditor and want to support spell-checking, auto-save, and syntax highlighting — in any combination. Subclassing every combination gives you seven classes for three features. Add a fourth feature and you double that number again. Decorator sidesteps this entirely: each feature lives in one class and wraps any other TextEditor.
Structure
- Component — the interface (or abstract class) shared by the real object and every decorator.
- ConcreteComponent — the base implementation you want to extend.
- BaseDecorator — holds a reference to a
Componentand delegates all calls; subclasses override only the methods they need to augment. - ConcreteDecorators — add the actual behavior before and/or after delegating.
A Text-Transformation Example
Start with a simple component interface and a plain implementation:
The base decorator stores the wrapped component and forwards every call to it:
Concrete decorators add behavior around that delegation:
Compose them in any order at the call site:
java.io — The Classic Real-World Decorator Stack
The entire java.io stream hierarchy is built on Decorator. InputStream is the Component; FileInputStream, ByteArrayInputStream, etc. are ConcreteComponents; and FilterInputStream is the BaseDecorator. Every class you layer on top — BufferedInputStream, DataInputStream, GZIPInputStream — is a ConcreteDecorator.
Each wrapper adds exactly one capability — buffering, charset decoding, gzip decompression, file reading — without any of the classes needing to know about each other. You can swap FileInputStream for ByteArrayInputStream (for tests) without touching any other layer.
Trade-offs and When to Use It
- Prefer it over inheritance when features are genuinely orthogonal and combinable.
- Prefer it over a single fat class that switches behavior with flags — decorators make each concern testable in isolation.
- Watch out for order sensitivity: trimming then uppercasing is not the same as uppercasing then trimming when locale-specific casing is involved.
- Equality and identity break:
decorated.equals(original)is almost alwaysfalse. Do not rely on object identity when decorators are in play. - Deep stacks are hard to debug: a chain of ten decorators can obscure the source of an unexpected result. Keep each decorator small and document the expected order.
Decorator with Java Functional Interfaces (Modern Alternative)
For simple single-method transformations, Function::andThen and Function::compose give you the same composition without any class hierarchy:
The class-based Decorator pattern remains the right tool when the component interface has multiple methods, when decorators carry state (e.g. a counting decorator), or when you need to pass the decorator through code that expects the component type.
Summary
Decorator wraps an object in another object that shares its interface, adding behavior without touching the original. java.io streams are its most famous application. Keep decorators stateless where possible, document composition order, and prefer the functional style for single-operation pipelines. The pattern shines whenever you have a set of independently useful behaviors that must combine freely at runtime.