Decorator Design Pattern

Classic GOF Structural Design Patterns

 
  

Intent

The Decorator Pattern is used where we need to attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

It is used when:

  • Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
  • you need to allow client-specified embellishment of a core object by recursively wrapping it
  • you want to extend an existing objects functionality on a case by case basis without modifying the base object or dealing with inheritance
  • you are dealing with a third party object that you want to add your own functionality to
decorator patterns
  

Problem

It is useful when you want to add behavior or state to individual objects at run-time - expecially where inheritance is not feasible because it is static and applies to an entire class.

  

Discussion

The Decorator changes the functionality of objects at runtime without impacting its blueprint or the existing functionality. While working with these patterns, we might confuse decorators with an inheritance. However, there is a difference between the two. Decorators are used when we have to add and remove responsibilities from an object dynamically. Whereas, inheritance can do the same, but not at run time.

Essentially, the Decorator Pattern relies on special objects called, aptly enough, decorators (or wrappers) - their purpose is to "wrap" the object so that it mimics its interface. This is important because it means that the client code will not know that you have handed it a wrapper rather than the original object. All wrappers have a field for storing an instance of the original object (the one eing decorated). Most wrappers initialize that field by getting passed the object to wrap in the constructor.

Remember that Decorators have the same interface as the object that they wrap. When a method is called on the decorator, it merely calls the same method on the wrapped object. So why even have the decorator? This is where decorators really shine - the call to the wrapped object could have additional logic executed both before and after it, adding all kinds of behavior to the original object without ever modifying it. Another thing to note is that Decorators can be nested on top of each other, with one calling another and so on before the final wrapped object is called - similar to those nested Russian Dolls (Matryoshka dolls). This allows you to create a number of basic, single-purpose decorators that, together, can add a tremendous amount of functionality to the original object.

A useful way to think about Decorators in the everyday world are clothes. Each human being is unique and starts out naked. Adding your first layer of clothes can change your appearance and behavior - a suit for a busy professional or celebratory day, or a tshirt and jeans for working in the yard. Each can make you feel and act differently. The clothes act as a decorator - adding and changing behaviors to the fundamental you. Remeber how we discussed that decorators can be nested? Clothes work EXACTLY the same way. Over those clothes, you can add a sweater or jacket to make you a little warmer. And over that, you could add a rain coat and even a pair of boots.

  

Structure

The structure of the Decorator is fairly straightforward in almost every programming language. Fundamentally, it consists of a decorator object that mimics the interfae of the Component that you are attempting to wrap. That decorator merely makes calls to the existing wrapped object.

Many programming languages allow you to use attributes to implement decorators rather than writing the wrapping logic directly - this certainly speeds up the process of developing decorators.


decorator pattern structure

The whole point of decorators is to add dynamic behavior to an object without modifying it. This is critically important when you start thinking about object models and the potential explosion of sub-classing that could take place if you were to go down an inheritance path. For example, you could have a object that represents a car. You wouldn't want to subclass it to support red and blue and green cars and every other color you could imagine- you would simply add a color decorator. This makes the code much more manageable and compact.

In this basic diagram of the Decorator Pattern the Client expects to call the Component in order to execute some logic or complete an operation. Instead, the client can call a ConcreteDecorator which can execute its additional functionality both before and after calling the original Component.

The structure is straightforward and consists of:

  • Component - defines the interfae for objects that can have responsibilities added to them dynamically.
  • ConcreteComponent - defines an object to which additional responsibilities can be attached.
  • Decorator - maintains a reference to the Component object and defines an interface that conforms to the Component's interface.
  • ConcreteDecorator - adds responsibilities and behaviors to the component.
  

Practical Example

For a practical example of the Decorator Pattern in a real world usage scenario, lets focus on a simple scenario that will measure the length of time it takes to call a method. Essentially, we are dealing with a metrics or measurement problem. We don't want to manually write code for every possible method and so we will use a decorator to help us with this measurement.

Let's walk through an example of how this would work. We are going to assume that the Component in our example is some critical piece of logic that we want to be able to time easily. We might be doing this because we wnt to monitor it's performance over time or get advanced notification when it takes too long.


gof decorator example

Much like the generic diagram in STRUCTURE above, the Decorator will provide a convenient way of adding functionality to an existing object, in this case by adding timing measurements..

Let's expand on this practical example with some code below.

  

Code Samples

Below, you will find examples of the Decorator implemented in four different commonly used languages. Each one of the examples implements the exact same scenario as described in the PRACTICAL EXAMPLE section above.

Each sample will use a Decorator to allow for easily adding the ability to measure the time it takes to executre a method. It should be noted that what is shown in the sample code may not be the most efficient method of measuring times in different programming languages. This is meant to demonstrate the usage of decorators and not necessarily the most efficient way to measure times.

It i also possible to take advantage of attribute-based decorators where it is supported in the language.


C# Decorator Code Sample

In this C# example, the Decorator pattern is used. Here we will demonstrate the basic approach to using a decorator.


Java Decorator Code Sample

In this Java example, the Decorator pattern is used. Here we will demonstrate the basic approach to using a decorator.


Typescript Decorator Code Sample

In this TypeScript example, the Decorator pattern is used. Here we will demonstrate the basic approach to using a decorator. This could easily also be done using an attribute-based approach (see my measures library for an example of this apporach).


JavaScript Decorator Code Sample

In this JavaScript example, the Decorator pattern is used. Because attributes are not directly supported in native JS, we will use the classic approach only for decorators.


  

Considerations

  1. Make sure that your task can be represented as one primary component and several optional extensions over it
  2. Create the Component interface that should describe all common methods for that primary component ant its extensions.
  3. Create the Concrete Component class and put the primary business logic in it.
  4. Create the Base Decorator class. Create a field for storing wrapped objects. It should have a Component interface type to allow storing both components and decorators inside.
  5. Make sure that all classes implement the Component interface.
  6. Make all the methods of the Base Decorator delegating execution to the methods of the wrapped object. It will allow Concrete Decorators to extend only a portion of the component's behavior and leave the rest intact.
  7. Create Concrete Decorator classes by extending them from the Base Decorator.
  8. A Concrete Decorator should execute its behavior prior or after the call to the same method in a wrapped object (you can just call a parent's method since it will end up calling the wrapped method).
  9. Client code must be responsible for configuring layers of wrapping. Client should work with all classes through a Component's interface to make the decorators interchangeable.
  

Additional Notes

  • Decorators can complicate the process of instantiating the component because you not only have to instantiate the component, but wrap it in a number of decorators.
  • It can be complicated to have decorators keep track of other decorators, because to look back into multiple layers of the decorator chain starts to push the decorator pattern beyond its true intent.
  • Decorator offers a pay-as-you-go approach to adding responsibilities. Instead of trying to support all foreseeable features in a complex, customizable class, you can define a simple class and add functionality incrementally with Decorator objects.
  • Adapter provides a different interface to its subject. Proxy provides the same interface. Decorator provides an enhanced interface.
  • Adapter changes an object's interface, Decorator enhances an object's responsibilities. Decorator is thus more transparent to the client. As a consequence, Decorator supports recursive composition, which isn't possible with pure Adapters.
  • Composite and Decorator have similar structure diagrams, reflecting the fact that both rely on recursive composition to organize an open-ended number of objects.
  • A Decorator can be viewed as a degenerate Composite with only one component. However, a Decorator adds additional responsibilities - it isn't intended for object aggregation.
  • Decorator is designed to let you add responsibilities to objects without subclassing. Composite's focus is not on embellishment but on representation. These intents are distinct but complementary. Consequently, Composite and Decorator are often used in concert.
  • Decorator and Proxy have different purposes but similar structures. Both describe how to provide a level of indirection to another object, and the implementations keep a reference to the object to which they forward requests.
  • Decorator lets you change the skin of an object. Strategy lets you change the internals.