Decorator
Decorator is a structural pattern that wraps an object in a same-interface wrapper to add functionality without modifying the original code. Because both the wrapper and the wrapped object implement the same interface, the client does not need to know whether it is using the original or a decorated version.
βΆArchitecture Diagram
π StructureDashed line animations indicate the flow direction of data or requests
When you want to add a feature to an existing class, the most intuitive approach is inheritance. But when feature combinations grow to 2, 3, or more, the number of subclasses explodes. Buffering + compression, buffering + encryption, compression + encryption, buffering + compression + encryption... creating a class for every combination makes the code blow up and maintenance effectively impossible. On top of that, inheritance is fixed at compile time. Dynamically toggling 'add caching to this request, remove it from that one' is hard to achieve with inheritance alone.
When GoF (Gang of Four) documented this pattern in 1994, the backdrop was a time when the limits of inheritance were becoming sharp in practice as object-oriented programming spread. In Smalltalk and early Java, designing GUI widgets and I/O streams by trying to represent every combination of features through inheritance hierarchies produced unmanageably deep class trees. Java's `java.io` package is the canonical example. The structure of wrapping an `InputStream` with `BufferedInputStream`, `DataInputStream`, and `GZIPInputStream` is a direct application of the Decorator pattern -- solving the problem through delegation and composition rather than inheritance.
Decorator's essence is wrapping. Think of gift wrapping: the item (the original component) is inside the box, and wrapping paper (decorators) can be layered on top. No matter how many layers of wrapping, from the outside it still looks like 'a box.' The structure has four roles. First, the Component interface defines the contract shared by the original and all decorators. Second, the ConcreteComponent is the original object containing the core logic. Third, the BaseDecorator holds a Component as a field, implements the same interface, and delegates requests to the inner Component. Fourth, ConcreteDecorators extend the BaseDecorator and insert their own logic before or after delegation. Execution flows from the outermost decorator inward through delegation, and results travel back outward with each layer applying its additional processing.
Decorator and Proxy share the structural similarity of wrapping another object and exposing the same interface. The difference is intent. Decorator wraps to add functionality; Proxy wraps to control access. Decorators are typically nested in multiple layers to create feature combinations, while Proxy is usually a single layer handling cross-cutting concerns like lazy creation, permission checks, or caching. Adapter can also cause confusion, but Adapter's purpose is to match an incompatible interface, so the inner and outer interfaces differ. Decorator keeps the interface the same and only extends behavior. If you need to change the interface of an existing object, that is Adapter; if you need to keep the interface and extend behavior, that is Decorator.
Commonly Compared Concepts
Proxy
A surrogate object that controls access to the real object
Both wrap an object and expose the same interface, but Decorator adds functionality while Proxy controls access.
Adapter
A structural pattern that bridges incompatible interfaces
Adapter converts an incompatible interface, while Decorator keeps the same interface and extends behavior.
Decorator appears naturally when feature combinations are numerous and creating a subclass for each combination is impractical. Java I/O streams are the most widely known application. Code like `new BufferedReader(new InputStreamReader(new FileInputStream(file)))` is decorator nesting in action. Middleware chains in web frameworks follow the same principle. As a request enters, it passes through authentication middleware, logging middleware, and compression middleware in order, with each stage processing or inspecting the request. Each middleware can be independently added or removed, and changing the order changes the behavior. The signal for adoption is clear: when the number of feature combinations grows and the inheritance hierarchy becomes unwieldy, when you need to attach or detach specific features at runtime, or when you must extend behavior without touching existing code. However, too many nested decorators can make debugging difficult because the call stack lengthens and identifying which layer caused a problem becomes harder.