目标

装璜器模式(Decorator Pattern) 的目标非常简单,那就是:

在不批改原有代码的状况下减少逻辑。

这句话听起来可能有些矛盾,既然都要减少逻辑了,怎么可能不去批改原有的代码?但 SOLID (向对象设计5大重要准则)的凋谢关闭准则就是在试图解决这个问题,其内容是不去改变曾经写好的外围逻辑,但又可能裁减新逻辑,也就是对扩大凋谢,对批改敞开。

举个例子,咱们想要实现一个专门在浏览器的控制台中输入文本的性能,可能会这样做:

class Printer {  print(text) {    console.log(text);  }}const printer = new Printer();printer.print('something'); // something

在你称心的看着本人的成绩时,产品过去说了一句:“我感觉色彩不够突出,还是把它改成黄色的吧!”

小菜一碟!你自信的关上百度一通操作之后,把代码改成了上面这样子:

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

但产品看了看又说:“这个字体有点太小了,再大一点,最好是高端大气上档次那种。

”好吧。。。“你强行管制着本人拿刀的激动,一边推敲多大的字体才是高端大气上档次,一边批改 print 的代码:

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

这次改完你之后你心中曾经满是 mmp 了,而且在不停的想:

“这样真的好吗?”

你无奈保障这次是最初的批改,而且也可能会不只一个产品来对你挤眉弄眼。你呆呆的看着显示器,直到电脑进入休眠模式,屏幕中映出你那张苦大仇深的脸,想着一直变得乌七八糟的 print 办法,不晓得该怎么去应酬那些永无休止的需要。。。

在下面的例子中,最开始的 Printer 依照需要写出它应该要有的逻辑,那就是在控制台中输入一些文本。换句话说,当写完“在 console 中输入一些文本”这段逻辑后,就能将 Printer 完结了,因为它就是 Printer 的全副了。那在这个状况下该如何扭转字体或是色彩的逻辑呢?

这时你该须要装璜器模式了。

Decorator Pattern(装璜器模式)

首先批改原来的 Printer,使它能够反对裁减款式:

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

之后别离创立扭转字体和色彩的装璜器:

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;`);  }});

代码中的 yellowStyleboldStylebigSizeStyle 别离是给 print 办法的装璜器,它们都会接管 printer,并以 printer 为根底复制出一个一样的对象进去并返回,而返回的 printer 与原来的区别是,各自 Decorator 都会为 printerprint 办法加上各自装璜的逻辑(例如扭转字体、色彩或字号)后再调用 printerprint

应用形式如下:

只有把所有装璜的逻辑抽出来,就可能自在的搭配什么时候要输入什么款式,退出要再减少一个斜体款式,也只须要再新增一个装璜器就行了,不须要改变原来的 print 逻辑。

不过要留神的是下面的代码只是简略的把 Object 用解构复制,如果在 prototype 上存在办法就有可能会出错,所以要深拷贝一个新对象的话,还须要另外编写逻辑:

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,就能正确复制雷同的对象了:

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

其余案例

因为咱们用的语言是 JavaScript,所以没有用到类,只是简略的装璜某个办法,比方上面这个用来公布文章的 publishArticle

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

如果你想要再公布文章之后在 微博或QQ空间之类的平台上发个动静,那又该怎么解决呢?是像上面的代码这样吗?

const publishArticle = () => {  console.log('公布文章');  console.log('发 微博 动静');  console.log('发 QQ空间 动静');};

这样显然不好!publishArticle 应该只须要公布文章的逻辑就够了!而且如果之后第三方服务平台越来越多,那 publishArticle 就会陷入始终加逻辑一爽快的状况,在明确了装璜器模式后就不能再这样做了!

所以把这个需要套上装璜器:

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));

后面 Printer 的例子是复制一个对象并返回,但如果是办法就不必复制了,只有确保每个装璜器都会返回一个新办法,而后会去执行被装璜的办法就行了。

总结

装璜器模式是一种十分有用的设计模式,在我的项目中也会常常用到,当需要变动时,感觉某个逻辑很多余,那么间接不装璜它就行了,也不须要去批改实现逻辑的代码。每一个装璜器都做他本人的事件,与其余装璜器互不影响。


本文首发微信公众号:前端先锋

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


欢送持续浏览本专栏其它高赞文章:

  • 深刻了解Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13个帮你进步开发效率的古代CSS框架
  • 疾速上手BootstrapVue
  • JavaScript引擎是如何工作的?从调用栈到Promise你须要晓得的所有
  • WebSocket实战:在 Node 和 React 之间进行实时通信
  • 对于 Git 的 20 个面试题
  • 深刻解析 Node.js 的 console.log
  • Node.js 到底是什么?
  • 30分钟用Node.js构建一个API服务器
  • Javascript的对象拷贝
  • 程序员30岁前月薪达不到30K,该何去何从
  • 14个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩大插件
  • Node.js 多线程齐全指南
  • 把HTML转成PDF的4个计划及实现

  • 更多文章...