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.
Creating one subclass per added feature works only while the combinations stay small. Once buffering, compression, and encryption can all be attached independently, class counts start growing with the number of combinations, and even reading class names stops telling you clearly which mix is supported. Early object-oriented GUI toolkits and I/O libraries ran straight into that limit.
Decorator comes from that pressure. Instead of multiplying inheritance branches, it layers wrappers around an object so features can be stacked through composition. Java's java.io package is the canonical example: InputStream wrapped by BufferedInputStream, DataInputStream, and GZIPInputStream is Decorator in production form.
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.
Decorator is strongest when the real problem is 'several behaviors need to be attached, but I do not want a class per combination.' Java I/O streams are the classic case. Code like new BufferedReader(new InputStreamReader(new FileInputStream(file))) composes buffering and character conversion by wrapping one layer at a time.
Middleware chains in web frameworks follow the same structure. A request is wrapped by authentication, logging, and compression in sequence, and any stage can be inserted or removed without rewriting the others. Change the order and the behavior changes with it, which is exactly the point of keeping the layers independent.
The adoption signal is equally clear: inheritance is no longer keeping up with feature combinations, runtime attachment or detachment matters, or existing behavior must be extended without editing the original code. The tradeoff is that too many wrappers make call flows harder to trace, so the composition count still needs to stay manageable.