共计 3563 个字符,预计需要花费 9 分钟才能阅读完成。
目标
装璜器模式(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;`);
}
});
代码中的 yellowStyle
、boldStyle
和 bigSizeStyle
别离是给 print
办法的装璜器,它们都会接管 printer
,并以 printer
为根底复制出一个一样的对象进去并返回,而返回的 printer
与原来的区别是,各自 Decorator
都会为 printer
的 print
办法加上各自装璜的逻辑(例如扭转字体、色彩或字号)后再调用 printer
的 print
。
应用形式如下:
只有把所有装璜的逻辑抽出来,就可能自在的搭配什么时候要输入什么款式,退出要再减少一个斜体款式,也只须要再新增一个装璜器就行了,不须要改变原来的 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 个计划及实现
- 更多文章 …