Decorator design pattern
The decorator pattern allows behaviour to be added to an existing object at runtime. This is achieved by wrapping the object (the component) in another class (the decorator).
Like the delegation pattern, the decorator pattern is similar to inheritance as it extends the functionality of a particular class. The primary difference between decorator and delegation is that the decorator pattern is more flexible, allowing multiple decorators to point to a single component at one time.
Reducing the complexity of an inheritance tree is one of the benefits to this pattern, imagine for example an application that prints a photo with an optional filter, an optional border and an optional rotation. This can be achieved in the following ways:
- A single class with a lot of code;
Photo
- A class for each combination of options;
Photo
,BorderPhoto
,FilterPhoto
,RotatePhoto
,BorderFilterPhoto
,FilterRotatePhoto
,BorderRotatePhoto
,BorderFilterRotatePhoto
- A class with optional decorators;
Photo
,PhotoFilterDecorator
,PhotoBorderDecorator
,PhotoRotateDecorator
The decorator pattern is of great use when implementing the open/closed principle, as described below:
software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
So once an application has been built, it should only be extended, not modified. It is particularly useful in a production environment as it reduces the amount of regression testing required. The decorator pattern is great here as it’s simple to extend existing components in a completely modular way.
Benefits
- Add behaviour to a component dynamically at runtime
- Attach multiple decorators to a single component at one time
- Completely modular, we don’t need to touch the existing component
- Can reduce the amount subclasses of a class
Drawbacks
- Overuse of the decorator pattern can lead to very abstract and complex code
UML diagram
Code example
public abstract class Decorator implements ComponentInterface {
private ComponentInterface component;
public Decorator(ComponentInterface component) {
this.component = component;
}
protected ComponentInterface getComponent() {
return component;
}
}
public interface ComponentInterface {
public String operation();
}
public class ConcreteComponent implements ComponentInterface {
public String operation() {
return "Component operation";
}
}
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(ComponentInterface component) {
super(component);
}
@Override
public String operation() {
return "Decorator operation";
}
}
// file: decorator.js
'use strict';
var Decorator = function (component) {
this.component = component;
};
Decorator.prototype.operation = function () {
return this.component.name + ' Decorator.operation';
};
module.exports = Decorator;
// file: component.js
'use strict';
var Component = function (name) {
this.name = name;
};
Component.prototype.operation = function () {
return this.name + ' Component.operation';
};
module.exports = Component;
Usage examples
Here are a few examples where you could use the decorator pattern:
- A photo (component) can be printed normally (concrete component), with a border (decorator), with a filter (decorator) or with a rotation (decorator).
- An employee (component) can have their name printed with no title (concrete component), as a developer (decorator) or as a manager (decorator).
- An email’s (component) contents can be constructed normally (concrete component), or with an optional header/footer describing the security level (decorators) or the organisation (decorator).