The Decorator Pattern
Learn about the Decorator design pattern and its implementation using a coding example.
We'll cover the following...
Overview
The Decorator pattern allows us to wrap an object that provides core functionality with other objects that alter this functionality. Any object that uses the decorated object will interact with it in exactly the same way as if it were undecorated (that is, the interface of the decorated object is identical to that of the core object).
There are two primary uses of the Decorator pattern:
- Enhancing the response of a component as it sends data to a second component
- Supporting multiple optional behaviors
The second option is often a suitable alternative to multiple inheritance. We can construct a core object, and then create a decorator wrapping that core. Since the decorator object has the same interface as the core object, we can even wrap the new object in other decorators. Here’s how it looks in a UML diagram:
Here, Core
and all the decorators implement a specific Interface
. The dashed lines show implements or realizes. The decorators maintain a reference to the core instance of that Interface via composition. When called, the decorator does some added processing before or after calling its wrapped interface. The wrapped object may be another decorator, or the core functionality. While multiple decorators may wrap each other, the object at the end of the chain of all those decorators provides the core functionality.
It’s essential that each of these is providing an implementation of a common feature. The intent is to provide a composition of processing steps from the various decorators, applied to the core. Often decorators are small, typically a function definition without any state.
In Python, because of duck typing, we don’t need to formalize these relationships with an official abstract interface definition. It’s sufficient to make sure the classes have matching methods. In some cases, we may define a typing.Protocol
as a type hint to help mypy reason about the relationships.
A Decorator example
Let’s look at an example from network programming. We want to build a small server that provides some data and a client that interacts with that server. The server will be simulating rolling complex handfuls of dice. The client will request a handful and wait for an answer that contains some random numbers.
This example has two processes interacting via a TCP socket, a way to transmit bytes among computer systems. Sockets are created by a server that listens for connections. When a client attempts to ...