Design – Structural Patterns

A decorator class must implement and use the interface the decorated class implements. Let’s see this step by step, starting with a non-decorated class design:

 Figure 11.1: A class diagram representing the ComponentA class implementing the IComponent interfaceFigure 11.1: A class diagram representing the ComponentA class implementing the IComponent interface 

In the preceding diagram, we have the following components:

  • A client that calls the Operation() method of the IComponent interface.
  • ComponentA, which implements the IComponent interface.

This translates into the following sequence diagram:

 Figure 11.2: A sequence diagram showing a consumer calling the Operation method of the ComponentA classFigure 11.2: A sequence diagram showing a consumer calling the Operation method of the ComponentA class 

Now, say that we want to add a behavior to ComponentA, but only in some cases. In other cases, we want to keep the initial behavior. To do so, we could choose the Decorator pattern and implement it as follows:

 Figure 11.3: Decorator class diagramFigure 11.3: Decorator class diagram 

Instead of modifying the ComponentA class, we created DecoratorA, which also implements the IComponent interface. This way, the Client object can use an instance of DecoratorA instead of ComponentA and leverage the new behavior without impacting the other consumers of ComponentA. Then, to avoid rewriting the whole component, an implementation of the IComponent interface (say ComponentA) is injected when creating a new DecoratorA instance (constructor injection). This new instance is stored in the component field and used by the Operation() method (implicitly using the Strategy pattern).We can translate the updated sequence like so:

 Figure 11.4: Decorator sequence diagramFigure 11.4: Decorator sequence diagram 

In the preceding diagram, instead of calling ComponentA directly, Client calls DecoratorA, which in turn calls ComponentA. Finally, DecoratorA does some postprocessing by calling its private method, AddBehaviorA().

Nothing from the Decorator pattern limits us from doing preprocessing, postprocessing, wrapping the decorated class’s call (the Operation method in this example) with some logic (like an if statement or a try-catch), or all of that combined. The use of adding a postprocessing behavior is only an example.

To show you how powerful the Decorator pattern is before we jump into the code, know this: we can chain decorators! Since our decorator depends on the interface (not the implementation), we could inject another decorator, let’s call it DecoratorB, inside DecoratorA (or vice versa). We could then create a long chain of rules that decorate one another, leading to a very powerful yet simple design.Let’s take a look at the following class diagram, which represents our chaining example:

 Figure 11.5: Decorator class diagram, including two decoratorsFigure 11.5: Decorator class diagram, including two decorators 

Here, we created the DecoratorB class, which looks very similar to DecoratorA but has a private AddBehaviorB() method instead of AddBehaviorA().

How we implement the decorator logic is irrelevant to the pattern, so I excluded the AddBehaviorA() method from Figure 9.3 to show you only the pattern. However, I added it to Figure 9.5 to clarify the idea behind having a second decorator.

Let’s take a look at the sequence diagram for this:

 Figure 11.6: Sequence diagram of two nested decoratorsFigure 11.6: Sequence diagram of two nested decorators 

With this, we are beginning to see the power of decorators. In the preceding diagram, we can assess that the behaviors of ComponentA have been changed twice without Client knowing about it. All those classes are unaware of the next IComponent in the chain. They don’t even know that they are being decorated. They only play their role in the plan—that’s all.It is also important to note that the decorator’s power resides in its dependency on the interface, not on an implementation, making it reusable. Based on that fact, we could swap DecoratorA and DecoratorB to invert the order the new behaviors are applied without touching the code itself. We could also apply the same decorator (say DecoratorC) to multiple IComponent implementations, like decorating both DecoratorA and DecoratorB. A decorator could even decorate itself.Let’s now dig into some code.

Leave a Reply

Your email address will not be published. Required fields are marked *



          Copyright © 2015-2024 | About | Terms of Service | Privacy Policy