一、前言: 裝飾者模式(Decorator Pattern):在不改變原類和繼承的情況下動態擴展對象功能,通過包裝一個對象來實現一個新的具有原對象相同介面的新的對象。 裝飾者模式的特點: 1. 在不改變原對象的原本結構的情況下進行功能添加。 2. 裝飾對象和原對象具有相同的介面,可以使客戶以與原對象 ...
一、前言:
裝飾者模式(Decorator Pattern):在不改變原類和繼承的情況下動態擴展對象功能,通過包裝一個對象來實現一個新的具有原對象相同介面的新的對象。
裝飾者模式的特點:
1. 在不改變原對象的原本結構的情況下進行功能添加。
2. 裝飾對象和原對象具有相同的介面,可以使客戶以與原對象相同的方式使用裝飾對象。
3. 裝飾對象中包含原對象的引用,即裝飾對象是真正的原對象經過包裝後的對象。
二、Javascript裝飾者模式詳解:
描述:
裝飾者模式中,可以在運行時動態添加附加功能到對象中。當處理靜態類時,這可能是一個挑戰。在Javascript中,由於對象是可變的,因此,添加功能到對象中的過程本身並不是問題。
裝飾者模式的一個比較方便的特征在於其預期行為的可定製和可配置特性。可以從僅具有一些基本功能的普通對象開始,然後從可用裝飾資源池中選擇需要用於增強普通對象的哪些功能,並且按照順序進行裝飾,尤其是當裝飾順序很重要的時候。
實現裝飾者模式的其中一個方法是使得每個裝飾者成為一個對象,並且該對象包含了應該被重載的方法。每個裝飾者實際上繼承了目前已經被前一個裝飾者進行增強後的對象。每個裝飾方法在“繼承的對象”上調用了同樣的方法並獲取其值,此外它還繼續執行了一些操作。
先上實例1:
//需要裝飾的類(函數) function Macbook() { this.cost = function () { return 1000; }; } //計算商品的包裝費 function PackagingFee(macbook) { this.cost = function () { return macbook.cost() + 75; }; } //計算商品的運費 function Freight(macbook) { this.cost = function () { return macbook.cost() + 300; }; } //計算商品的保險費用 function Insurance(macbook) { this.cost = function () { return macbook.cost() + 250; }; } // 用法 var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook()))); console.log(myMacbook.cost());//1625
我們簡單的分析下上面的代碼,上面的代碼中,一共定義了四個函數(其中一個需要修飾的函數,三個用於修飾的函數)。
然後,聲明一個變數myMacbook指向new出來的Insurance對象,Insurance對象的形參指向new出來的Freight對象,Freight對象的形參指向new出來的PackagingFee對象,PackagingFee對象的形參指向new出來的Macbook對象。
接下來,調用myMacbook的cost方法。從上面的分析,我們可以得出 myMacbook.cost()的值等於(Freight對象的cost方法+250),Freight對象的cost方法等於(PackagingFee對象的cost方法+300),PackagingFee對象的cost方法等於(Macbook對象的cost方法+75)。
所以最終的結果是:myMacbook.cost()的值 = 250 + (300 + (75 + 1000)) = 1625。
// 用法 var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook()))); console.log(myMacbook.cost());//1625 //上面的代碼等價於下麵拆分後的代碼,或許拆分後代碼你更能看出前後的邏輯性 var macbook = new Macbook(); var package = new PackagingFee(macbook); var freight = new Freight(package); var myMacbook = new Insurance(freight); //當然,如果你不想聲明這麼多變數(macbook、package、freight),只用一個變數也是可以的 var macbook = new Macbook(); macbook = new PackagingFee(macbook); macbook = new Freight(macbook); var myMacbook = new Insurance(macbook);
再看看實例2:
function ConcreteClass() { this.performTask = function () { this.preTask(); console.log('doing something'); this.postTask(); }; } function AbstractDecorator(decorated) { this.performTask = function () { decorated.performTask(); }; } function ConcreteDecoratorClass(decorated) { this.base = AbstractDecorator; this.base(decorated);// add performTask method decorated.preTask = function () { console.log('pre-calling..'); }; decorated.postTask = function () { console.log('post-calling..'); }; } var concrete = new ConcreteClass(); var decorator1 = new ConcreteDecoratorClass(concrete); decorator1.performTask(); //pre-calling.. //doing something //post-calling..實例2實際上和實例1是非常類似的,我們來簡單分析下吧。首先,實例2中定義了三個函數,然後聲明瞭兩個變數concrete和decorator1,最後調用了decorator1的performTask方法。
粗看一眼,ConcreteDecoratorClass裡面好像並沒有performTask方法。我們先來分析下麵的兩行代碼:
var concrete = new ConcreteClass(); //聲明一個變數concrete指向new出來的ConcreteClass對象 var decorator1 = new ConcreteDecoratorClass(concrete); //聲明一個變數decorator1指向new出來的ConcreteDecoratorClass對象,並傳入變數concrete作為形參然後,我們再來逐行分析下ConcreteDecoratorClass函數裡面的代碼:
this.base = AbstractDecorator; //定義一個當前對象(decorator1)的base屬性,並指向函數AbstractDecorator this.base(decorated); //調用base屬性指向的函數,也就是調用AbstractDecorator函數,同時傳入形參decorated,形參decorated指向new出來的ConcreteClass對象說到這裡,好像還是沒有分析出ConcreteDecoratorClass函數裡面有performTask方法,重點是看 "this"!
ConcreteDecoratorClass函數中的this指向new出來的ConcreteDecoratorClass對象(也就是和decorator1指向同一個對象);
AbstractDecorator函數裡面的this關鍵是看哪個對象來調用這個函數,this就指向哪個對象(從代碼 “this.base = AbstractDecorator; this.base(decorated);” 中我們可以看出是new出來的ConcreteDecoratorClass對象在調用AbstractDecorator函數),所以AbstractDecorator函數裡面的this指向new出來的ConcreteDecoratorClass對象(也和decorator1指向同一個對象)。
總結下來,我們會發現,在上面的代碼中,不管是ConcreteDecoratorClass函數裡面的this,還是AbstractDecorator函數裡面的this,都指向new出來的ConcreteDecoratorClass對象。
所以,當我們執行decorator1.performTask()時,它會繼續執行匿名函數中的代碼(decorated.performTask();),匿名函數中的decorated形參指向new出來的ConcreteClass對象,並執行該對象的performTask方法。
最後看看實例3:
var tree = {}; tree.decorate = function () { console.log('Make sure the tree won\'t fall'); }; tree.getDecorator = function (deco) { tree[deco].prototype = this; return new tree[deco]; }; tree.RedApples = function () { this.decorate = function () { this.RedApples.prototype.decorate(); // 第7步:先執行原型(這時候是Angel了)的decorate方法 console.log('Add some red apples'); // 第8步 再輸出 red // 將這2步作為RedApples的decorate方法 } }; tree.BlueApples = function () { this.decorate = function () { this.BlueApples.prototype.decorate(); // 第1步:先執行原型的decorate方法,也就是tree.decorate() console.log('Put on some blue apples'); // 第2步 再輸出blue // 將這2步作為BlueApples的decorate方法 } }; tree.Angel = function () { this.decorate = function () { this.Angel.prototype.decorate(); // 第4步:先執行原型(這時候是BlueApples了)的decorate方法 console.log('An angel on the top'); // 第5步 再輸出angel // 將這2步作為Angel的decorate方法 } }; tree = tree.getDecorator('BlueApples'); // 第3步:將BlueApples對象賦給tree,這時候父原型里的getDecorator依然可用 tree = tree.getDecorator('Angel'); // 第6步:將Angel對象賦給tree,這時候父原型的父原型里的getDecorator依然可用 tree = tree.getDecorator('RedApples'); // 第9步:將RedApples對象賦給tree tree.decorate(); // 第10步:執行RedApples對象的decorate方法 //Make sure the tree won't fall //Add blue apples //An angel on the top //Put on some red apples實例3看起來很複雜,實際上分析邏輯還是和前面兩個實例一樣,我們可以看出實例3中一共聲明瞭5個函數表達式。我們重點分析下下麵的代碼:
//tree.getDecorator('BlueApples')返回new出來的tree.BlueApples的實例對象,並將該對象賦值給空的tree對象 tree = tree.getDecorator('BlueApples'); //new出來的tree.BlueApples的實例對象的原型指向 --> 空對象tree //tree.getDecorator('Angel')返回new出來的tree.Angel的實例對象(這行代碼中的第二個tree已經是上面一行代碼運行結果後的tree.BlueApples的實例對象) tree = tree.getDecorator('Angel'); //new出來的tree.Angel的實例對象的原型指向 --> tree.BlueApples的實例對象 //tree.getDecorator('RedApples')返回new出來的tree.RedApples的實例對象(這行代碼中的第二個tree已經是上面一行代碼運行結果後的tree.Angel的實例對象) tree = tree.getDecorator('RedApples'); //new出來的tree.RedApples的實例對象的原型指向 --> tree.Angel的實例對象 //調用tree.decorate(),這裡的tree已經是new出來的tree.RedApples的實例對象了。 //tree.RedApples的實例對象的decorate屬性方法裡面的第一行代碼是 “this.RedApples.prototype.decorate()” //結合上面的分析可以得出以下的原型鏈結構: //this.RedApples.prototype --> tree.Angel; //tree.Angel.prototype --> tree.BlueApples; //tree.BlueApples.prototype --> 空對象tree tree.decorate();
分析到這裡,就不難知道最後的輸出結果了。
三、其他:
我們可以看出本文章中的裝飾者模式案例中用了很多this,對this不太瞭解的朋友可以移步到 《你真的懂javascript中的 “this” 嗎?》。
本文案例建議複製下來逐行分析,趕緊行動起來吧!