What is decorator pattern

Decorator pattern is a technique for adding features to a function or class, allowing us to add new capabilities and behaviors to an object without modifying the original object. It is also essentially a function (in javascipt, a class is just syntactic sugar for a function).

When can we get it

Let’s consider a scenario where a bike store has several models of bikes, and now the store allows users to provide some additional accessories for each type of bike, such as headlights, taillights, bells, etc. Every choice of one or more accessories will affect the price of the bike.

If we subclass the more traditional way, we currently have a bike base class, and we create a new class for each possible choice. But because the user can choose one or several arbitrary accessories, this leads to the end may produce dozens or hundreds of subclasses, which is obviously unscientific. However, in this case, we can use the decorator pattern to solve the problem.

The basic classes of bicycles are as follows:

class Bicycle {
    // Other methods
    wash () {}
    ride () {}
    getPrice() {
        return 200; }}Copy the code

So we can start by creating a decorator pattern base class

class BicycleDecotator {
    constructor(bicycle) {
        this.bicycle = bicycle;
    }
    wash () {
        return this.bicycle.wash();
    }
    ride () {
        return this.bicycle.ride();
    }
    getPrice() {
        return this.bicycle.getPrice(); }}Copy the code

The base class doesn’t really do anything, it just takes an instance of Bicycle, implements its corresponding method, and calls its method back.

Once we have this base class, we can do whatever we want with the original Bicycle class. For example, I can create a decorator with headlights and a decorator with taillights:

class HeadLightDecorator extends BicycleDecorator {
    constructor(bicycle) {
        super(bicycle);
    }
    getPrice() {
        return this.bicycle.getPrice() + 20; }}class TailLightDecorator extends BicycleDecorator {
    constructor(bicycle) {
        super(bicycle);
    }
    getPrice() {
        return this.bicycle.getPrice() + 20; }}Copy the code

Then, we can combine them freely:

let bicycle = new Bicycle();
console.log(bicycle.getPrice()); / / 200
bicycle = new HeadLightDecorator(bicycle); // Add headlights to the bike
console.log(bicycle.getPrice());  / / 220
bicycle = new TailLightDecorator(bicycle); // Add headlights and taillights to the bike
console.log(bicycle.getPrice()); / / 240
Copy the code

So what’s the advantage of this? Let’s say we have 10 accessories, then we only need to write 10 accessory decorators, and then we can match any bike with different accessories and calculate the price. With the subclass implementation, 10 widgets might have hundreds or even thousands of subclasses.

From the example we can see where the decorator pattern applies:

  1. The decorator pattern should be used if you need to add features or responsibilities to a class, but the solution of subclassing from the class is not practical.
  2. In our example, we did not modify the original Bicycle base class, so there are no side effects to the original code. We just added some features to the original. Therefore, if you want to add features to an object without changing the code that uses the object, you can use the decorator pattern.

The decorator pattern applies not only to classes but also to functions (which are, in fact, higher-order functions). For example, if we want to measure the execution time of a function, I can write a decorator like this:

function func() {
    console.log('func');
}
function timeProfileDecorator(func) {
    return function (. args) {
        const startTime = new Date(a); func.call(this. args);const elapserdTime = (new Date()).getTime() - startTime.getTime();
        console.log('The function is consumed${elapserdTime}ms`); }}const newFunc = timeProfileDecorator(func);
console.log(newFunc());
Copy the code

Do something fun

Now that we know that the Decorator pattern can add new functionality to the original code without changing it, we can do some interesting things.

We can provide performance analysis capabilities for a class’s methods.

class TimeProfileDecorator {
  constructor(component, keys) {
    this.component = component;
    this.timers = {};
    const self = this;
    for (let i in keys) {
      let key = keys[i];
        if (typeof component[key] === 'function') {
          this[key] = function(. args) {
            this.startTimer(key);
            // Fix this reference errorcomponent[key].call(component, ... args);this.logTimer(key);
          }
        }
    }
  }
  startTimer(namespace) {
    this.timers[namespace] = new Date(a); } logTimer(namespace) {const elapserdTime = (new Date()).getTime() - this.timers[namespace].getTime();
    console.log('The function is consumed${elapserdTime}ms`); }}// example
class Test {
  constructor() {
    this.name = 'cjg';
    this.age = 22;
  }
  sayName() {
    console.log(this.name);
  }
  sayAge() {
    console.log(this.age); }}let test1 = new Test();
test1 = new TimeProfileDecorator(test1, ['sayName'.'sayAge']);
console.log(test1.sayName());
console.log(test1.sayAge());
Copy the code

Enhance the function

  1. Throttling function or anti-shake function

    function throttle(func, delay) {
        const self = this;
        let tid;
        return function(. args) {
            if (tid) return;
            tid = setTimeout((a)= >{ func.call(self, ... args); tid =null; }, delay); }}function debounce(func, delay) {
        const self = this;
        let tid;
        return function(. args) {
            if (tid) clearTimeout(tid);
            tid = setTimeout((a)= >{ func.call(self, ... args); tid =null; }, delay); }}Copy the code
  2. The cache function returns a value

    // Cache the result of a function, for some computation-intensive function effect is obvious.
    function memorize(func) {
    	const cache = {};
        return function (. args) {
            const key = JSON.stringify(args);
            if (cache[key]) {
              console.log('Cached');
              return cache[key];
            }
            const result = func.call(this. args); cache[key] = result;return result;
        };
    }
    
    function fib(num) {
      return num < 2 ? num : fib(num - 1) + fib(num - 2);
    }
    
    const enhanceFib = memorize(fib);
    console.log(enhanceFib(40));
    console.log(enhanceFib(40));
    console.log(enhanceFib(40));
    console.log(enhanceFib(40));
    Copy the code

React high-order components add additional functionality to components, such as shallowCompare for components:

import React from 'react';
const { Component } = react;

const ShadowCompareDecorator = (Instance) = > class extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return! shallowCompare(this.props, nextProps) || ! shallowCompare(this.state, nextState);
  }
  render() {
    return (
      <Instance {. this.props} / >); }}; export default ShadowCompareDecorator;Copy the code

Of course, if you’ve ever used React-Redux, you’ve probably also used Connect. Connect is also a higher-order component approach. It gets the global state from the Provider context through the decorator pattern and passes it to the original component as props.

conclusion

Using decorator mode allows us to add new functionality to existing classes and functions without modifying the existing code or changing the way it is called, thus causing no side effects to the existing system. We don’t have to worry that the original system will fail or be incompatible because of it. Personally, I find this a particularly useful design pattern.

The good news is that js decorators have been added to the ES7 draft. It allows us to use decorator mode more elegantly, and if you are interested, add Babel’s plugins to try it out in advance. Ruan Yifeng teacher’s tutorial is also very easy to understand.

Click “like” if you think it’s helpful. thank you

This article address is in -> my blog address, welcome to give a start or follow reference:

Javascript Design pattern