MixDecorator: An enhaced version of the Decorator pattern

TOC

MixDecorator is enhanced version of the classical Decorator pattern defined in Gang of Four book.

The classical Decorator pattern offers a solution to extend the functionality of an object in order to modify its behavior. Still, when we want to add new responsibilities, and not just to change the behavior of an existing one, the classical Decorator pattern allows us to define such a decoration, but this is accessible only if it is the last added one.

MixDecorator does not just eliminate some constraints of the classical pattern (e.g. limitation to one interface), but also allows it to be used as a base for a general extension mechanism. This version introduces significant flexibility and abstraction.

Using it, we may:

  • combine different responsibilities,
  • have direct access to all, and
  • operate with them in any order

Decorator pattern

The classical Decorator pattern offers a solution to extend the functionality of an object in order to modify its behaviour.

It is usually agreed that decorators and the original class object share a common set of operations:

multiple decorators can be stacked on top of each other, each time adding new functionality to the overridden method(s).

Limitations of the classical Decorator pattern.

Theoretically, we may obtain any combination of decorations but we only have the base class interface available.

So, if there are some responsibilities that are really new responsibilities (that changes the object

interface) and they are not used just to alter the behavior of the operations defined in the base class, they will be accessible only if the last decoration is the one that defines them.

More concretely, if responsibility f1 is a new interface responsibility and it is defined in the class Decorator1, then the corresponding message could be sent only to an object that has the Decorator1 decoration, but also only if this is the last added decoration; the following Java code snippet emphasizes this:

The emphasized solution has obviously several drawbacks:

  • we have to apply an additional operation that allows decoration removal getBase(); in case we don’t have it, there is no solution;
  • the functionality added by Decorator2 is lost (the behavior modification of the base operations brought by Decorator2);
  • if there are several decorations that should be removed then several additional operations are necessary;
  • if we don’t know the exact position (order) of the searched decoration, the code becomes very complex (a kind of reflection should be used);

In fact, removing the last decorations in order to reveal functionality is not a real solution since it breaks the way in which decorated objects are supposed to be used. Also, it is an ad-hoc workaround that is based on knowing the order in which decorations were added.

Concrete example: Java Input Streams

We may consider the classes in Java IO streams library, where Decorator /span> pattern is used.

FilterInputStream corresponds there to DecoratorBase and it’s derived from InputStream that corre sponds to IComponent. There are several decoration classes derived from FilterInputStream such as PushBackInputStream that defines a method unread(), which is not defined in the FilterInputStream interface; BufferedInputStream that just alters the behavior of the standard InputStream interface; or CheckedInputStream that maintains a checksum of the data being read and
allows using it based on the method getChecksum.

We may combine them, and decorate a concrete stream – e.g. FileInputStream – first with CheckedInputStream, and then with BufferedInputStream or/and with PushBackInputStream; then the getChecksum method is not directly
available.

MixDecorator Pattern

Synopsis. Attach a set of additional responsibilities to an object dynamically. Allow access to all added responsibilities. Provide a flexible alternative to subclassing for extending objects functionality and their types, too (extending the set of messages that could be sent to them).

Context. You want to add a combination of additional capabilities onto an object. However, the additional capabilities you want are highly variable, and could extend the interface of the base object.

You want all additional responsibilities to be directly available.

Problem. The classical Decorator pattern offers a solution to extend (decorate) the functionality of a certain object in order to modify its behavior. It is usually agreed that decorators and the original class object share a common interface. The problem appears when we want to add a new interface responsibility, and not just to change the behavior of an existing
one. Such a decoration could bedefined, but it is accessible only if it is the last added one.

Forces.

  • Adding responsibilities should be transparent to clients.
  • All responsibilities which are added at some point are directly accessible to the client.
  • Simple and direct call for all decoration methods, not dependent of the exact position (order) of
  • the decoration where the desired responsibility is defined.
  • It should be easy to change, e.g. withdraw or adding, responsibilities.
  • Good effciency, and easily extendable.

Solution.
The structure of the MixDecorator is inspired by the Decorator but there are several important differences that allow the achievement of the ‘forces’. As for simple decorators we enclose the subject in another object, the decorator object, but the decorator could have an interface that extends the general component interface. The decorator forwards requests to the subject while performing additional actions before and after forwarding.

The solution structure is:

This makes a clear separation between IComponent and DecoratorBase by introducing a general IDecorator interface that extends IComponent and adds only getBase()method (this method is considered mandatory).

The concrete class DecorateBase has almost the same deffnition as the corresponding class from the classical Decorator (the difference is the additional method getBase()).

For a particular application, after the new responsibilities are inventoried, then particular IDecoratorOperations and ConcreteDecoratorBase are defined. IDecoratorOperations defines the methods that correspond to all new responsibilities. As it can be seen from the figure, ConcreteDecoratorBase is derived from DecoratorBase but also implements IDecoratorOperations.

The concrete class ConcreteDecoratorBase gives a definition for each new added responsibility. It implements IDecoratorOperations and extends DecoratorBase. The corresponding code hides a recursion that is used for searching the method (for example f1()) that defines the new responsibility.

In order to better explain the pattern we will give some implementation details in Java 8.

(Instead of try-catch mecahanism, instanceof operator could be used).

The following code snippet emphasizes the forces fulfillment; the execution throws no exception, and it can be noticed that, for example, f3() could be called even if Decorator3 is the first added decoration

Extensions with other responsibilities

If other possible responsibilities are discovered as being appropriate to be used, these could be added using the following steps:

  1. (optional) Provide an adaptation that assures that all the responsibilities either added in the first design iteration or in the next, could be combined in any order.
    Define a new interface – IDecoratorOperations_Extended that extends IDecoratorOperations interface, and defines the desired new responsibilities.
  2. (optional) Provide an adaptation that assures that all the responsibilities either added in the first design iteration or in the next, could be combined in any order.
    Define a class – ConcreteDecoratorBase_Extended that extends ConcreteDecoratorBase and implements IDecoratorOperations_Extended.
  3. (optional) Provide an adaptation that assures that all the responsibilities either added in the first design iteration or in the next, could be combined in any order.

The specified adaptation could be done using, for example, Adapter pattern.

The previous decoration classes are adapted to the new extended interface.

For example the class Decorator2_Adapted
is derived from Decorator2, and implements IDecoratorOperations_Extended; no method overriding is necessary.

But this also requires a basic implementation of the methods defined in IDecoratorOperations_Extended.

Java 8 introduces default methods in interfaces.The implementation of the method f4() in IDecoratorOperations_Extended could be defined in Java as:

This definition assures that the first set of decorations could be combined with the new decorations in any order,inclusive to wrap around the new decorations (optional step (3)).

The implementation of the class ConcreteDecoratorBase_Extended is similar to that of ConcreteDecoratorBase.

Usage example:

The code produces the correct execution of all the methods.

Implementation

The general structure of the pattern could be easily implemented in any object-oriented language.

In order to allow new decoration extensions, there is an implementation requirement defined by the possibility of adding new methods to an interface (to add a set of methods to IDecoratorOperations interface), and also to provide a basic implementation for them.

Classically, this is done based on multiple inheritance.

So, a language as C++ or any other that allows multiple inheritance lead to a simple implementation, where IDecoratorOperations_Extended is defined as an abstract class.

Other mechanisms – specific to the target language – could be investigated.

An example is provided by the Java extended interfaces (or virtual extension methods – as they are also called).

They are based on defining default implementations inside interfaces (the implementation details for our casewere given in the previous section). It is also considered that Java 8 interfaces can be exploited to introduce a trait-oriented programming style.

Also, the implementation of the MixDecorator could be simplified by considering only IDecoratorOperations without ConcreteDecoratorBase.

In Java 8 this would be an interface with default methods (with the same definitions as they are now in ConcreteDecoratorBase).

In C# the pattern could be implemented based on extension methods. Using “extension methods” we are able to add new methods to a class after the complete definition of the class. They allow the extension of an existing type with new functionality, without having to sub-class or recompile the old type. The mechanism allows only static binding and so the methods that could be added to a class cannot be declared virtual. In fact, an extension method is a static method defined in a non-generic static class, and can be invoked using an instance method syntax. If we use them in C#, we may add a static class Decorator_Extension where the methods f4(), f5() are defined as extension methods. The class Decorator_Extension provides extension for IDecoratorOperations:

The code defines a recursion that it’s stopped either if the current decoration defines the invoked method or by throwing an exception if no decoration that defines such a method was found.

In Java, the verification of the first case is implicitly done based on polymorphic call.

With this C# solution, no adaptation of the first decorator classes is needed

MixDEcorator – Advantages

  • The linear combination of the decorations is hidden. The final object could be seen as an object with a set of additional responsibilities. This way we may consider that we extend the type of an object (by extending the set of messages that could be sent to it) without defining new classes.
  • The component and its decorators are decoupled. The author of the component does not need to do anything special for it to be decorated.
  • Added behavior could be used in any combination, without any additional operation: such as withdrawing decorations.
  • Clients can choose what capabilities they want by sending messages to the object that has the appropriate decoration.

Examples

The applications that were designed based on the classical Decorator, and which define new responsibilities for the decorated objects, could be improved by using MixDecorator.

The definition of Java IO streams is such an example.

If a method of type getBase() would be provided in FilterInputStream then MixDecorator could be used instead of simple Decorator, and so we could eliminate the constraints of the current implementation.

Many other similar situations could be found, where decorations imply also adding new operations.

Framework for collection data structures with features

We proposed in [2] a design based on Decorator pattern for frameworks of collection data structures. The implementation and testing of the proposed design has been done in Java. The analysis showed that Decorator has several drawbacks if it is applied in the classical way. The further analysis leaded to the MixDecorator pattern.

The pattern has been used in order to allow the creation of new collections based on dynamic composition of the features that characterize the corresponding data structures. In this way we may add or remove features dynamically.

The fact that only linear combinations of features are allowed could be seen as a disadvantage, but since the classical version of Decorator has been replaced with MixDecorator this disadvantage is hidden: the features with changed interface are visible even if they are not added as a final decoration.

Here the advantage of using the presented pattern is very clear. Without this pattern the framework design and usage would have been much more difficult.

Refferences

[1] Virginia Niculescu. MixDecorator: An Enhanced Version of the Decorator Pattern. In Proceedings 20th European Conference on Pattern Languages of Programs (EuroPLoP’2015) Kloster Irsee, Germany 8-12 July 2015 (link)

[2] V. Niculescu, D. Lupsa, A Decorator Based Design for Collections, Proceedings of KEPT 2013: The Fourth International Conference On Knowledge Engineering, Principles and Techniques (June 2013) Studia Universitatis “Babes-Bolyai”, Informatica, Volume LVIII, Number 3 (Sept. 2013). pp. 54-64 (.pdf)