Decorator

Attach additional responsibilities to an object dynamically.Decorators provide a flexible alternative to subclassing for extending functionality.

Purpose is born

Imagine that you are working with a notification library that allows other programs to notify their users about important events.

The initial version of the library is based on the Notifier class with only a few fields, a constructor, and a single submit method. This method can accept a notification argument from a client application and send the notification to a list of emails that have been passed to the notifier via its constructor. A third-party application that acts as a client must create and configure the notifier object once and then use it every time something important happens.

At some point, you realize that library users don't just want to receive email notifications. Many people also want to receive messages from SMS, Facebook, Slack...

You have extended the Notifier class and included additional notification methods in new subclasses. Now the client must instantiate the desired notification class and use it for all subsequent notifications.

But then someone reasonably asked you, “Why can't you use multiple notification types at once?”

You tried to solve that problem by creating special subclasses that combine multiple notification methods in one class. However, it quickly becomes clear that this approach will make your source code unnecessarily large and complex, not only for the library but also for the user.

The Decorator Pattern can solve this problem by leaving the email notification method inside the base notification class, but moving all other notification methods inside decorators.

The client code will need to contain a basic notification object as a set of user-dependent decorators. Decorator objects will be structured as a stack.

Real-World Analogy

Wearing clothes is an example of using decorators. When you’re cold, you wrap yourself in a sweater. If you’re still cold with a sweater, you can wear a jacket on top. If it’s raining, you can put on a raincoat. All of these garments “extend” your basic behavior but aren’t part of you, and you can easily take off any piece of clothing whenever you don’t need it.

Structure

Components in the model:

  • Component: is an interface that defines common methods required for all components participating in this template.

  • Concrete Component: is a class that implements Component's methods.

  • Decorator: is an abstract class used to maintain a reference of the Component object and at the same time implement methods of the Component interface.

  • Concrete Decorator: is a class that implements Decorator methods, it installs new features for Component.

  • Client: Component user object with attached extension requirements.

Advantages & disadvantages

Advantage

  • You can extend an object's behavior without creating a new subclass.

  • You can add or remove features from an object during execution.

  • An object can be wrapped by multiple wrappers at the same time.

  • Single Responsibility Principle - Multiple implementations of a method in a class can be divided into many smaller classes.

Disadvantages

  • It is difficult to remove a specific wrapper from the stack.

  • It is difficult to implement a decorator in such a way that its methods do not depend on the order in the stack.

When to use it

  • When you want to add new features to objects without affecting these objects.

  • When it is not possible to extend an object by inheritance. For example, if a class uses the final keyword, the only way to extend this class is to use a decorator.

  • In some cases, using inheritance will take a lot of effort in writing code.

Applicability

  • Use the Decorator pattern when you need to be able to assign extra behaviors to objects at runtime without breaking the code that uses these objects.

    • The Decorator lets you structure your business logic into layers, create a decorator for each layer and compose objects with various combinations of this logic at runtime. The client code can treat all these objects in the same way, since they all follow a common interface.

  • Use the pattern when it’s awkward or not possible to extend an object’s behavior using inheritance.

    • Many programming languages have the final keyword that can be used to prevent further extension of a class. For a final class, the only way to reuse the existing behavior would be to wrap the class with your own wrapper, using the Decorator pattern.

How to Implement

  1. Make sure your business domain can be represented as a primary component with multiple optional layers over it.

  2. Figure out what methods are common to both the primary component and the optional layers. Create a component interface and declare those methods there.

  3. Create a concrete component class and define the base behavior in it.

  4. Create a base decorator class. It should have a field for storing a reference to a wrapped object. The field should be declared with the component interface type to allow linking to concrete components as well as decorators. The base decorator must delegate all work to the wrapped object.

  5. Make sure all classes implement the component interface.

  6. Create concrete decorators by extending them from the base decorator. A concrete decorator must execute its behavior before or after the call to the parent method (which always delegates to the wrapped object).

  7. The client code must be responsible for creating decorators and composing them in the way the client needs.

Relations with Other Patterns

  • Adapter provides a completely different interface for accessing an existing object. On the other hand, with the Decorator pattern the interface either stays the same or gets extended. In addition, Decorator supports recursive composition, which isn’t possible when you use Adapter.

  • With Adapter you access an existing object via different interface. With Proxy, the interface stays the same. With Decorator you access the object via an enhanced interface.

  • Chain of Responsibility and Decorator have very similar class structures. Both patterns rely on recursive composition to pass the execution through a series of objects. However, there are several crucial differences.

    The CoR handlers can execute arbitrary operations independently of each other. They can also stop passing the request further at any point. On the other hand, various Decorators can extend the object’s behavior while keeping it consistent with the base interface. In addition, decorators aren’t allowed to break the flow of the request.

  • Composite and Decorator have similar structure diagrams since both rely on recursive composition to organize an open-ended number of objects.

    A Decorator is like a Composite but only has one child component. There’s another significant difference: Decorator adds additional responsibilities to the wrapped object, while Composite just “sums up” its children’s results.

    However, the patterns can also cooperate: you can use Decorator to extend the behavior of a specific object in the Composite tree.

  • Designs that make heavy use of Composite and Decorator can often benefit from using Prototype. Applying the pattern lets you clone complex structures instead of re-constructing them from scratch.

  • Decorator lets you change the skin of an object, while Strategy lets you change the guts.

  • Decorator and Proxy have similar structures, but very different intents. Both patterns are built on the composition principle, where one object is supposed to delegate some of the work to another. The difference is that a Proxy usually manages the life cycle of its service object on its own, whereas the composition of Decorators is always controlled by the client.

Last updated