9

purpose

The purpose of the Decorator Pattern is very simple, that is:

Add logic without modifying the original code.

This sentence may sound a bit contradictory. Since logic is needed to be added, how can it be possible not to modify the original code? But the open and closed principle of SOLID (5 Important Principles for Object Design) is trying to solve this problem. Its content is not to change the core logic that has been written, but to expand the new logic, that is, open to extension and open to modification. shut down.

For example, if we want to implement a function to output text in the browser console, we might do this:

class Printer {
  print(text) {
    console.log(text);
  }
}

const printer = new Printer();
printer.print('something'); // something

When you looked at your results with satisfaction, the product came over and said: "I think the color is not prominent enough, so let's change it to yellow!"

A piece of cake! After you open the Baidu One-Pass operation with confidence, change the code to the following:

class Printer {
  print(text) {
    console.log(`%c${text}`,'color: yellow;');
  }
}

image.png

But after looking at the product, he said: "This font is a bit too small, and bigger, it's best to be a high-end style.

"Okay..." You are forcibly controlling your urge to take a knife, while figuring out how big the font is for the high-end atmosphere, you modify the code of print

class Printer {
  print(text) {
    console.log(`%c${text}`,'color: yellow;font-size: 36px;');
  }
}

image.png

After changing you this time, your mind is full of mmp, and you keep thinking:

"Is this really good?"

You cannot guarantee that this is the last modification, and there may be more than one product that will point you at your fingertips. print until the computer went into sleep mode, and your bitter face was reflected on the screen, thinking about the 060e26eb968312 method that keeps getting messy, and don’t know how to deal with those never-ending demands. . . .

In the above example, the initial Printer writes out the logic it should have according to the requirements, that is, output some text in the console. In other words, after writing the logic of "output some text in the console", you can Printer , because it is all of Printer How to change the font or color logic in this situation?

At this time you need decorator mode.

Decorator Pattern

First modify the original Printer that it can support extended styles:

class Printer {
  print(text = '', style = '') {
    console.log(`%c${text}`, style);
  }
}

Then create a decorator that changes the font and color:

const yellowStyle = (printer) => ({
  ...printer,
  print: (text = '', style = '') => {
    printer.print(text, `${style}color: yellow;`);
  }
});

const boldStyle = (printer) => ({
  ...printer,
  print: (text = '', style = '') => {
    printer.print(text, `${style}font-weight: bold;`);
  }
});

const bigSizeStyle = (printer) => ({
  ...printer,
  print: (text = '', style = '') => {
    printer.print(text, `${style}font-size: 36px;`);
  }
});

Code yellowStyle , boldStyle and bigSizeStyle are to print method decorators, they are received printer , and to printer based reproduce a different object and returned, and the return printer original difference is that each Decorator will is printer the print method having the respective logical decorative (e.g., change the font, size or color) and then call printer of print .

The usage is as follows:

image.png

As long as you extract all the decoration logic, you can freely match when to output which style, add another italic style, and only need to add another decorator, without changing the original print logic.

However, it should be noted that the above code simply copies Object by destructuring. If prototype , there may be errors, so if you want to deep copy a new object, you need to write additional logic:

const copyObj = (originObj) => {
  const originPrototype = Object.getPrototypeOf(originObj);
  let newObj = Object.create(originPrototype);
   
  const originObjOwnProperties = Object.getOwnPropertyNames(originObj);
  originObjOwnProperties.forEach((property) => {
    const prototypeDesc = Object.getOwnPropertyDescriptor(originObj, property);
     Object.defineProperty(newObj, property, prototypeDesc);
  });
  
  return newObj;
}

copyObj in the above code in the decorator to copy the same object correctly:


const yellowStyle = (printer) => {
  const decorator = copyObj(printer);

  decorator.print = (text = '', style = '') => {
    printer.print(text, `${style}color: yellow;`);
  };

  return decorator;
};

Other cases

Because the language we use is JavaScript, we don't use classes, we just simply decorate a certain method, such as the following publishArticle used to publish articles:

const publishArticle = () => {
  console.log('发布文章');
};

If you want to post an update on a platform such as Weibo or Qzone after publishing an article, what should you do? Is it like the following code?

const publishArticle = () => {
  console.log('发布文章');

  console.log('发 微博 动态');
  console.log('发 QQ空间 动态');
};

This is obviously not good! publishArticle should only need the logic of publishing the article! And if there are more and more third-party service platforms in the publishArticle , then 060e26eb96867f will fall into a situation where logic is always added, and you can no longer do this after you understand the decorator mode!

So put this requirement into a decorator:

const publishArticle = () => {
  console.log('发布文章');
};

const publishWeibo = (publish) => (...args) => {
  publish(args);
  console.log('发 微博 动态');
};

const publishQzone = (publish) => (...args) => {
  publish(args);
  console.log('发 QQ空间 动态');
};


const publishArticleAndWeiboAndQzone = publishWeibo(publishQzone(publishArticle));

The previous Printer is to copy an object and return it, but if it is a method, there is no need to copy it. Just make sure that each decorator will return a new method and then execute the decorated method.

to sum up

The decorator pattern is a very useful design pattern, and it is often used in projects. When the requirements change, if you feel that there is a lot of logic, then you can just just not decorate it, and you don’t need to modify the code that implements the logic. . Each decorator does its own thing and does not affect each other with other decorators.

173382ede7319973.gif


This article first published WeChat public account: Front-end Pioneer
Welcome to scan the QR code to follow the official account, and push you fresh front-end technical articles every day

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


Welcome to continue reading other highly praised articles in this column:



疯狂的技术宅
44.4k 声望39.2k 粉丝