Channels ▼
RSS

Web Development

Decorator Pattern in JavaScript


Decorator is a Gang-of-Four design pattern that allows additional functionality to be added to an object dynamically, at run time. When dealing with static classes, this could be a challenge. In JavaScript, objects are mutable, so the process of adding functionality to an object is not a problem.

A convenient feature of the decorator pattern is the customization and configuration of the expected behavior. You start with your plain object, which has some basic functionality. Then you pick and choose from an available pool of decorators which ones you want to use to enhance your plain object and in which order, if the order is important.

Usage

Say you're working on a Web application that sells something. Every new sale is a new sale object. The sale "knows" about the price of the item and can return it by calling the sale.getPrice() method. Depending on the circumstances, you can start decorating this object with extra functionality.

Imagine a scenario where the sale for a customer is in the Canadian province of Quebec. In this case, the buyer needs to pay a federal tax and a provincial Quebec tax. Following the decorator pattern, you'll say that you "decorate" the object with a federal tax decorator and a Quebec tax decorator. You can then also decorate the object with price-formatting functionality. This scenario could look like the following:

var sale = new Sale(100);       // the price is 100 dollars
sale = sale.decorate('fedtax'); // add federal tax
sale = sale.decorate('quebec'); // add provincial tax
sale = sale.decorate('money');  // format like money
sale.getPrice();                // "$112.88"

In another scenario, the buyer could be in a province that doesn't have a provincial tax, and you might also want to format the price using Canadian dollars, so you can do:

var sale = new Sale(100);       // the price is 100 dollars
sale = sale.decorate('fedtax'); // add federal tax
sale = sale.decorate('cdn');    // format using CDN
sale.getPrice();                // "CDN$ 105.00"

As you can see, this is a flexible way to add functionality and tweak an object at run time. Let's see how to approach an implementation of the pattern.

Implementation

One way to implement the decorator pattern is to have each decorator be an object containing the methods that should be overwritten. Each decorator actually inherits the object enhanced so far after the previous decorator. Each decorated method calls the same method on the uber (the inherited object), gets the value, and proceeds with doing something in addition.

The end effect is that when you do sale.getPrice() in the first example, you're calling the method of the money decorator, as shown in Figure 1. But because each decorated method first calls the parent's method, money's getPrice() first calls quebec's getPrice()/, which in turn calls fedtax's getPrice(), and so on. The chain goes all the way up to the original undecorated getPrice() implemented by the Sale() constructor.


Figure 1: Implementation of the decorator pattern.

The implementation starts with a constructor and a prototype method:

function Sale(price) {
  this.price = price || 100;
}
Sale.prototype.getPrice = function () {
  return this.price;
};

The decorator objects will all be implemented as properties of a constructor property:

Sale.decorators = {};

Let's see one example decorator. It's an object that implements the customized getPrice() method. Note that the method first gets the value from the parent method and then modifies that value:

Sale.decorators.fedtax = {
  getPrice: function () {
    var price = this.uber.getPrice();
    price += price * 5 / 100;
    return price;
  }
};


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV