TheDecoratorPattern

From Pattern Repository Wiki
Jump to: navigation, search

The Decorator Pattern

Webinar Link

Contextual Forces

Motivation

Add optional, additional behaviors to an entity dynamically. Ideally the design should allow for a wide variety of combinations of these additional behaviors without altering the clients that consume the results.

Encapsulation

The number (cardinality) and order (sequence) of the optional behaviors. Ideally, the fact that a decorator is being used can also be hidden from the client. In all the runtime variations shown below, the Client takes the same action:

Decorator 0 text.jpg

Procedural Analog

A "stack" of simple conditionals is analogous to one main aspect of the decorator; that is the ability to add one, many, all, or none of a set of optional behaviors.

//Non-optional behavior here
if(conditionA) {
     // Optional Behavior A
}

if(conditionB) {
     // Optional Behavior B
}
//Non-optional behavior here

This procedural approach does not allow for variations in the order of the behaviors, however. It is, therefore, only a partial analog.

Non-Software Analog

Those who have used a single-lens reflex camera have experienced a design that is very much like the Decorator Pattern.

Decorator as camera.JPG

Such a camera has a basic behavior set that is required for it to fulfill its function in photography:

  1. It allows light to enter the camera, through a lens.
  2. It focuses the light at a fixed point called the "backplane" where the film is held rigid.
  3. It opens the shutter for a controllable and reliable period of time.
  4. It advances the film.

The first step of this process can be altered without changing anything about the camera itself, by using a filter, or any number of filters, that change the light before it enters the camera. The resulting photograph will be different, but the camera (and the outside world) remains unchanged.

The filters are designed to be attached to the front of the camera lens. They have male threads on their trailing edges, which mate to the female threads on the leading edge of the lens housing. However, they also have these female threads on their leading edges as well, and so a filter can be attached to a lens, or to another filter, allowing them to be stacked up. The overall effect on the resulting photograph is the accumulated effect of all the filters currently included in the stack.

A camera can take a photograph without any filters at all; it is sufficient by itself.

A filter cannot accomplish any kind of photograph alone. It must always be attached to something; either another filter, or the camera lens.

Implementation Forces

Example

Decorator 1.jpg

public class TargetAbstraction {
     public abstract void m(par p);
}

public class BaseBehavior extends TargetAbstraction {
     public void m(par p) {
           // Base Behavior
     }
}

public abstract Decorator extends TargetAbstraction {
     private TargetAbstration myTrailer;
     public Decorator(TargetAbstraction aTrailer) {
           myTrailer = aTrailer;
     }
     public void m(par p) {
           myTrailer.m(p);
     }
}

public class DecoratorA extends Decorator {
     public DecoratorA(TargetAbstraction aTrailer) {
           superclassConstructor(aTrailer);
     }
     public void m(par p) {
           // Decorator A behavior here
           superclass.m(p);
     }
}

public class DecoratorB extends Decorator {
     public DecoratorB TargetAbstraction aTrailer) {
           superclassConstructor(aTrailer);
     }
     public void m(par p) {
           // Decorator B behavior here
           superclass.m(p);
     }
}

Questions, concerns, credibility checks

  1. Can the various decorators and the base behavior share a common interface? If there are differences between them, how will this be resolved without sacrificing any key capabilities?
  2. What is the structure of the collection for the decorators and the base behavior? The classic implementation uses a linked list (see above), but a decorator can also be implemented using an array, a Vector, or any suitable collection.
  3. Do the methods being decorated have a void return (aggregating behaviors in a pass-down the chain), or is there a return from them? If there is a return, then each decorator will have two opportunities to add behavior, as the calls propagate down the chain, and as the returns propagate back up. (See Options in implementation for more on this issue.)
  4. Can the decorator be totally encapsulated? That is, can the client be designed to use any combination of decorators, and/or the base behavior, in precisely the same way? (See Options in implementation for more on this issue.)
  5. Are their particular combinations of decorators and/or particular sequences that should be prevented? If so, an object factory to build only the legitimate decorator chains is advised. In this case, if the restriction is highly critical, all concrete classes (everything apart from the Client and the TargetAbstraction above) can be private inner classes of the factory.

Options in implementation

An alternate form of the decorator uses a method or methods that have a return. This creates three different types of decorators; those that act "on the way down", those that act "one the way back up" and those that do both.

Decorator 2.jpg

'The "Bucket Brigade" form of the Decorator Pattern'

These three types can then be implemented for different behaviors by subclassing. Also, a void return decorator can be implemented with similar behavior, by coding some decorators to simply wait until the "next call" returns before they take action. This behavior is simply easier to see and understand when we discuss it in terms of a non-void return.

Sometimes one of the decorators is used to add methods to the interface of the target abstraction. When this is done, the client is exposed to the presence of the decorator, the specific decorating type, and the fact that this particular decorator must be first in the chain.

An example of this is the way streaming I/O is done in Java (this example is not literally accurate, but is the general design of the I/O API in Java and similar languages):

Decorator 3.jpg

A similar approach is taken to the OutputStream type. This creates an important bit of functionality in that it allows developers to use this API without requiring them to compose and decompose their data into bytes before using the IO capabilities of the framework. The "Data" version of the decorator does this for them.

The downside is that the Client must hold this decorator in a downcast to its specific type, and therefore it cannot be encapsulated. Furthermore, the Client must hold this reference directly:

Decorator 4.jpg

'Case 2 is an error'

The first case will work, but the second will not, because the client has no reference to the DataInputStream instance, and therefore cannot use the expanded interface (it consequently provides no value).

In other words, the DataInputStream must be the first object in the chain. The design of the decorator, the specific decorator being used, and the presence of the pattern are all visible to the client, and are therefore not encapsulated. This was considered a worthwhile trade-off given the importance of the "Data" interface.

If, however, we consider the patterns as collections of forces, as we are suggesting here, then there is another alternative. Reusing behavior with a changed interface is a contextual force of the Adapter Pattern, and if we think of our problem in this way, the following design comes to mind:

Decorator 5.jpg

'Decorator with Adapter'

This keeps the decorator design encapsulated, and only exposes the Adapter, which of course the client would have to have visibility to (this is a force in the Adapter, after all).

Consequent Forces

Testing issues

The base behavior can be tested in a normal way, but the decorators impose a bit of a challenge in that they are designed to 'decorate something'. If we wish to test them in isolation from one another, then we can use a Mock object to stand in place of the next object in the chain. This allows us to inspect the effect of the decorator being tested, by examining the state of the Mock or Fake object after the decorator behaves.

Decorator 6.jpg

If DecoratorA, for instance, was designed to take a string of characters and convert them to Unicode, then DecoratorATest (above) would hand DecoratorA a string of plain 7-bit ASCII characters, and then check the Mock to ensure that it was handed that same string in Unicode. Thus, an 'inspectable' mock is needed.

Cost-Benefit (gain-loss)

  1. The design is extremely flexible, allowing for different combinations of additional behavior, different number of behaviors, and different sequences to put them in.
  2. Avoids creating a feature-laden class. Features can be easily added with decorators.
  3. Promotes strong cohesion.
  4. New decorators can be derived later, leaving the design very open-closed.
  5. The design can confuse developers who are not familiar with it.
  6. Some combinations of decorators may be unwise, buggy, or violate business rules.