Decorator

The decorator pattern is used to wrap and augment an existing class. Using a decorator pattern is an alternative to subclassing an existing component. Subclassing is typically a compile time operation and is a tight coupling. This means that once subclassing is performed, there is no way to alter it at runtime. In cases where there are many possible subclassings that can act in combination, the number of combinations of subclassings explodes. Let's look at an example.

The armor worn by knights in Westeros can be quite configurable. Armor can be fabricated in a number of different styles: scale, lamellar, chainmail, and so on. In addition to the style of armor, there is also a variety of different face guards, knee, and elbow joints, and, of course, colors. The behavior of armor made from lamellar and a grille is different from chainmail with a face visor. You can see, however, that there is a large number of possible combinations; far too many combinations to explicitly code.

What we do instead is implement the different styles of armor using the decorator pattern. A decorator works using a similar theory to the adapter and bridge patterns, in that it wraps another instance and proxy calls through. The decorator pattern, however, performs the redirections at runtime by having the instance to wrap passed into it. Typically, a decorator will act as a simple pass through for some methods and for others it will make some modifications. These modifications could be limited to performing an additional action before passing the call off to the wrapped instance or could go so far as to change the parameters passed in. A UML representation of the decorator pattern looks like the following diagram:

Decorator

This allows for very granular control over which methods are altered by the decorator and which remain as mere pass-through. Let's take a look at an implementation of the pattern in JavaScript.

Implementation

In this code we have a base class, BasicArmor, and it is then decorated by the ChainMail class:

class BasicArmor {
  CalculateDamageFromHit(hit) {
    return hit.Strength * .2;
  }
  GetArmorIntegrity() {
    return 1;
  }
}

class ChainMail {
  constructor(decoratedArmor) {
    this.decoratedArmor = decoratedArmor;
  }
  CalculateDamageFromHit(hit) {
    hit.Strength = hit.Strength * .8;
    return this.decoratedArmor.CalculateDamageFromHit(hit);
  }
  GetArmorIntegrity() {
    return .9 * this.decoratedArmor.GetArmorIntegrity();
  }
}

The ChainMail armor takes in an instance of armor that complies with an interface, such as:

export interface IArmor{
  CalculateDamageFromHit(hit: Hit):number;
  GetArmorIntegrity():number;
}

That instance is wrapped and calls proxied through. The method GetArmorIntegiry modifies the result from the underlying class while CalculateDamageFromHit modifies the arguments that are passed into the decorated class. This ChainMail class could, itself, be decorated with several more layers of decorators until a long chain of methods is actually called for each method call. This behavior, of course, remains invisible to outside callers.

To make use of this armor decorator, look at the following code:

let armor = new ChainMail(new Westeros.Armor.BasicArmor());
console.log(armor.CalculateDamageFromHit({Location: "head", Weapon: "Sock filled with pennies", Strength: 12}));

It is tempting to make use of JavaScript's ability to rewrite individual methods on classes to implement this pattern. Indeed, in an earlier draft of this section I had intended to suggest just that. However, doing so is syntactically messy and not a common way of doing things. One of the most important things to keep in mind when programming is that code must be maintainable, not only by you but also by others. Complexity breeds confusion and confusion breeds bugs.

The decorator pattern is a valuable pattern for scenarios where inheritance is too limiting. These scenarios still exist in JavaScript, so the pattern remains useful.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset