Decorator Pattern

The Decorator Pattern allows adding new functionalities to an existing object without altering its structure. This type of design pattern is part of the structural patterns and is used to wrap existing classes. This pattern creates a decorator class to wrap the original class and provide additional functionality while maintaining the integrity of the class methods.

Description

There are generally two ways to add behavior to a class or object:

  • Inheritance mechanism: Using inheritance is an effective way to add functionality to an existing class. By inheriting an existing class, a subclass can have its own methods as well as access the methods of the parent class. However, this method is static, and users cannot control the way and timing of adding behavior.
  • Association mechanism: This involves embedding an object of one class into another object, and letting the containing object decide whether to call the embedded object's behavior to extend its own behavior. This embedded object is referred to as a Decorator.

The decorator pattern dynamically attaches additional responsibilities to an object transparently to the client. In other words, the client does not perceive any difference between the object before and after decoration. The decorator pattern can extend an object's functionality without the need to create more subclasses.

Pattern Structure

  • Component: Abstract component.
  • ConcreteComponent: Concrete component.
  • Decorator: Abstract decorator class.
  • ConcreteDecorator: Concrete decorator class.

Advantages

  • The decorator pattern provides more flexibility than inheritance in extending an object's functionality.
  • It allows dynamically extending an object's functionality. Different decorators can be selected at runtime through configuration files to achieve different behaviors.
  • By using different concrete decorators and combining them, many different combinations of behaviors can be created, and multiple concrete decorators can be used to decorate the same object, resulting in a more powerful object.
  • Concrete component classes and concrete decorator classes can evolve independently. Users can add new concrete component classes and concrete decorator classes as needed, and combine them at runtime without changing the existing code, thus complying with the Open/Closed Principle.

Disadvantages

  • Designing a system using the decorator pattern will produce many small objects. The difference between these objects lies in the way they are interconnected, rather than differences in their classes or properties. This will also result in many concrete decorator classes, increasing the complexity of the system and making it more difficult to learn and understand.
  • The more flexible nature of the decorator pattern compared to inheritance also means that it is more prone to errors and debugging difficulties. For an object with multiple decorators, debugging to find errors may require pinpointing the issues step by step, which can be cumbersome.

Applicable Scenarios

  • Adding responsibility to a single object dynamically and transparently without affecting other objects.
  • Needing to dynamically add and remove functionality from an object.
  • When it's not feasible to extend the system using inheritance, or when using inheritance hinders system extension and maintenance. There are two main situations where inheritance cannot be used: first, when there are a large number of independent extensions in the system, leading to an explosive growth in the number of subclasses to support each combination; and second, when class definitions cannot be inherited, such as for final classes.

Implementation

class Component{
    say(){
        throw new Error("Abstract method cannot be called");
    }
}

class ConcreteComponent extends Component{
    say(){
        console.log("ConcreteComponent");
    }
}

class Decorator extends Component{
    constructor(concreteComponent){
        super();
        this.concreteComponent = concreteComponent;
    }
    say(){
        this.concreteComponent.say();
    }
}

class ConcreteDecorator extends Decorator{
    constructor(concreteComponent){
        super(concreteComponent);
    }
    say(){
        console.log("Decoratored ConcreteComponent");
    }
}

(function(){
    var concreteComponent = new ConcreteComponent();
    concreteComponent.say(); // ConcreteComponent
    var decoratoredConcreteComponent = new ConcreteDecorator(concreteComponent);
    decoratoredConcreteComponent.say(); // Decoratored ConcreteComponent
})();

Daily Question

https://github.com/WindrunnerMax/EveryDay

References

https://imweb.io/topic/5b1403bbd4c96b9b1b4c4e9e https://www.runoob.com/design-pattern/decorator-pattern.html https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/decorator.html