這需求快讓我崩潰了,不過(guò)幸虧我懂裝飾器模式
目的
裝飾器模式(Decorator Pattern) 的目的非常簡(jiǎn)單,那就是:
在不修改原有代碼的情況下增加邏輯。 |
這句話聽(tīng)起來(lái)可能有些矛盾,既然都要增加邏輯了,怎么可能不去修改原有的代碼?但 SOLID (向?qū)ο笤O(shè)計(jì)5大重要原則)的開(kāi)放封閉原則就是在試圖解決這個(gè)問(wèn)題,其內(nèi)容是不去改動(dòng)已經(jīng)寫(xiě)好的核心邏輯,但又能夠擴(kuò)充新邏輯,也就是對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。
舉個(gè)例子,假如產(chǎn)品的需求是實(shí)現(xiàn)一個(gè)專(zhuān)門(mén)在瀏覽器的控制臺(tái)中輸出文本的功能,你可能會(huì)這樣做:
- class Printer {
- print(text) {
- console.log(text);
- }
- }
- const printer = new Printer();
- printer.print('something'); // something
在你滿意的看著自己的成果時(shí),產(chǎn)品過(guò)來(lái)說(shuō)了一句:“我覺(jué)得顏色不夠突出,還是把它改成黃色的吧!”
小菜一碟!你自信的打開(kāi)百度一通操作之后,把代碼改成了下面這樣子:
- class Printer {
- print(text) {
- console.log(`%c${text}`,'color: yellow;');
- }
- }
但產(chǎn)品看了看又說(shuō):“這個(gè)字體有點(diǎn)太小了,再大一點(diǎn),最好是高端大氣上檔次那種。
”好吧。。。“你強(qiáng)行控制著自己拿刀的沖動(dòng),一邊琢磨多大的字體才是高端大氣上檔次,一邊修改 print 的代碼:
- class Printer {
- print(text) {
- console.log(`%c${text}`,'color: yellow;font-size: 36px;');
- }
- }
這次改完你之后你心中已經(jīng)滿是 mmp 了,而且偷偷給產(chǎn)品貼了個(gè)標(biāo)簽:
你無(wú)法保證這次是最后的修改,而且也可能會(huì)不只一個(gè)產(chǎn)品來(lái)對(duì)你指手劃腳。你呆呆的看著顯示器,直到電腦進(jìn)入休眠模式,屏幕中映出你那張苦大仇深的臉,想著不斷變得亂七八糟的 print 方法,不知道該怎么去應(yīng)付那些永無(wú)休止的需求。。。
在上面的例子中,最開(kāi)始的 Printer 按照需求寫(xiě)出它應(yīng)該要有的邏輯,那就是在控制臺(tái)中輸出一些文本。換句話說(shuō),當(dāng)寫(xiě)完“在控制臺(tái)中輸出一些文本”這段邏輯后,就能將 Printer 結(jié)束了,因?yàn)樗褪? Printer 的全部邏輯了。那在這個(gè)情況下該如何改變字體或是顏色的邏輯呢?
這時(shí)你該需要裝飾器模式了。
Decorator Pattern(裝飾器模式)
首先修改原來(lái)的 Printer,使它可以支持?jǐn)U充樣式:
- class Printer {
- print(text = '', style = '') {
- console.log(`%c${text}`, style);
- }
- }
之后分別創(chuàng)建改變字體和顏色的裝飾器:
- 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 方法的裝飾器,它們都會(huì)接收 printer,并以 printer 為基礎(chǔ)復(fù)制出一個(gè)一樣的對(duì)象出來(lái)并返回,而返回的 printer 與原來(lái)的區(qū)別是,各自 Decorator 都會(huì)為 printer 的 print 方法加上各自裝飾的邏輯(例如改變字體、顏色或字號(hào))后再調(diào)用 printer 的 print。
使用方式如下:
只要把所有裝飾的邏輯抽出來(lái),就能夠自由的搭配什么時(shí)候要輸出什么樣式,加入要再增加一個(gè)斜體樣式,也只需要再新增一個(gè)裝飾器就行了,不需要改動(dòng)原來(lái)的 print 邏輯。
不過(guò)要注意的是上面的代碼只是簡(jiǎn)單的把 Object 用解構(gòu)復(fù)制,如果在 prototype 上存在方法就有可能會(huì)出錯(cuò),所以要深拷貝一個(gè)新對(duì)象的話,還需要另外編寫(xiě)邏輯:
- 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;
- }
然后裝飾器內(nèi)改使上面代碼中的 copyObj,就能正確復(fù)制相同的對(duì)象了:
- const yellowStyle = (printer) => {
- const decorator = copyObj(printer);
- decorator.print = (text = '', style = '') => {
- printer.print(text, `${style}color: yellow;`);
- };
- return decorator;
- };
其他案例
因?yàn)槲覀冇玫恼Z(yǔ)言是 JavaScript,所以沒(méi)有用到類(lèi),只是簡(jiǎn)單的裝飾某個(gè)方個(gè)方法,比如下面這個(gè)用來(lái)發(fā)布文章的 publishArticle:
- const publishArticle = () => {
- console.log('發(fā)布文章');
- };
如果你想要再發(fā)布文章之后在 微博或QQ空間之類(lèi)的平臺(tái)上發(fā)個(gè)動(dòng)態(tài),那又該怎么處理呢?是像下面的代碼這樣嗎?
- const publishArticle = () => {
- console.log('發(fā)布文章');
- console.log('發(fā) 微博 動(dòng)態(tài)');
- console.log('發(fā) QQ空間 動(dòng)態(tài)');
- };
這樣顯然不好!publishArticle 應(yīng)該只需要發(fā)布文章的邏輯就夠了!而且如果之后第三方服務(wù)平臺(tái)越來(lái)越多,那 publishArticle 就會(huì)陷入一直加邏輯一直爽的情況,在明白了裝飾器模式后就不能再這樣做了!
所以把這個(gè)需求套上裝飾器:
- const publishArticle = () => {
- console.log('發(fā)布文章');
- };
- const publishWeibo = (publish) => (...args) => {
- publish(args);
- console.log('發(fā) 微博 動(dòng)態(tài)');
- };
- const publishQzone = (publish) => (...args) => {
- publish(args);
- console.log('發(fā) QQ空間 動(dòng)態(tài)');
- };
- const publishArticleAndWeiboAndQzone = publishWeibo(publishQzone(publishArticle));
前面 Printer 的例子是復(fù)制一個(gè)對(duì)象并返回,但如果是方法就不用復(fù)制了,只要確保每個(gè)裝飾器都會(huì)返回一個(gè)新方法,然后會(huì)去執(zhí)行被裝飾的方法就行了。
總結(jié)
裝飾器模式是一種非常有用的設(shè)計(jì)模式,在項(xiàng)目中也會(huì)經(jīng)常用到,當(dāng)需求變動(dòng)時(shí),覺(jué)得某個(gè)邏輯很多余,那么直接不裝飾它就行了,也不需要去修改實(shí)現(xiàn)邏輯的代碼。每一個(gè)裝飾器都做他自己的事情,與其他裝飾器互不影響。