JS設計模式七:裝飾者模式
裝飾者模式,Decorator Pattern。在不改變原類和繼承的情況下動態擴充項物件功能,通過封裝一個對象來實現一個新的具有原對象相同介面的新的對象。
裝飾者模式特點: 1. 不修改原對象的原本結構來進行功能添加。
2. 裝飾對象和原對象具有相同的介面,可以使客戶以與原對象相同的方式使用裝飾對象。
3. 裝飾對象中包含原對象的引用,即裝飾對象為真正的原對象在此封裝的對象。
裝飾者模式可以為對象添加功能從而代替了編寫大量子類的情況。
以JS設計模式執行個體為例,首先單車商店有4種類型單車:
- var ABicycle = function(){ ... };
- var BBicycle = function(){ ... };
- var CBicycle = function(){ ... };
- var DBicycle = function(){ ... };
當單車商店需要為每類單車推出附加功能或者配件,比如前燈,車籃,改色等的時候,其最基本的方式就是為每一種組合建立一個子類,如有鈴鐺的A類車,黃色的B類車,有車籃的C類車等等,
- var ABicycleWithBell = function(){ ... };
- var BBicycleWithColorYellow = function(){ ... };
- var CBicycleWithColorBule = function(){ ... };
- ...
的確,就猶如你第一眼看到的感覺一樣,這搞法果斷不靠譜,n種配置,到最後就會產生4n+4種類(包括原始的4個父類),那是決然不可取的。
當然我們可能的第一想法便是可以將顏色,車籃什麼的設為執行個體屬性,在類中由傳遞的參數進行控制產生,這樣的確不錯沒什麼問題,不過對於一些功能上的擴充可能就心有餘而力不足了,比如單車添加變速功能,刹車加強功能,防盜功能等,那麼對於添加這些功能使用裝飾者模式則更為簡便了(如果說將更能內建於類中,再由傳參進行處理,在功能較多的情況下顯然也比較不靠譜)。
首先需要一個基礎單車A的類
- function ABicycle(){ }
- ABicycle.prototype = {
- wash : function(){ },
- ride : function(){ },
- getPrice : function(){
- return 999;
- }
- }
接下來就是給單車裝上鈴鐺,響鈴的功能:
最簡單的應該就是直接封裝對象執行個體:
- function bicycleBell( bicycle ){
- var price= bicycle.getPrice();
- bicycle.bell = function(){
- console.log("ding! ding! ding!");
- };
- bicycle.getPrice = function(){
- return price + 100;
- };
- return bicycle;
- }
那麼使用
- var bicycleA = new ABicycle();
- bicycleA = bicycleBell( bicycleA );
如果是下面這種形式
- bicycle.getPrice = function(){
- return bicycle.getPrice() + 100;
- };
這種形式當然就會導致無限迴圈調用~
的確這種封裝方法不是將對象再次封裝成建構函式,僅僅只是調整執行個體對象,既方便又簡單,不過對於類似getPrice方法這種返回常量資料結果的倒是問題也不大,可以使用如上面閉包形式的方式來儲存。
但如果對於其功能的在封裝需要調用,再用閉包形式來先進行原始方法的儲存,就會趕腳很繁瑣不變。
那麼可以來看一下下面這種裝飾方式:
- function BicycleBell( bicycle ){
- this.bicycle = bicycle;
- }
- BicycleBell.prototype = {
- wash : function(){
- return this.bicycle.wash();
- },
- ride : function(){
- return this.bicycle.ride();
- },
- getPrice : function(){
- return this.bicycle.ride() + 100;
- },
- bell : function(){
- console.log("ding! ding! ding!");
- }
- }
封裝執行個體,再次類比原始類,將執行個體作為參數封裝,提供與原始類一樣的介面。這種方式很好的解決了對於某些需要修改並且依賴原始該方法的方法產生形式。當然其也有自己的缺點,這個裝飾者太過於繁瑣,所有的,比如加速,切換顏色,車籃什麼的裝飾者每一個都必須如此形式的話勢必代碼有點雜亂多。
那麼,提取出來吧
首先需要有一個繼承的方法,並且有指向父類的prototype
- function extend( subClass, superClass ){
- var F = function(){};
- F.prototype = superClass.prototype;
- subClass.prototype = new F();
- subClass.prototype.constructor = subClass;
- // 此處指向superClass的prototype 比直接儲存superClass使用更為方便
- subClass.superclass = superClass.prototype;
- if( superClass.prototype.constructor === Object.prototype.constructor ){
- superClass.prototype.constructor = superClass;
- }
- }
再來一個各個裝飾者可以依賴繼承的中間裝飾者:
- function BicycleDecorator( bicycle ){
- this.bicycle = bicycle;
- }
- BicycleDecorator.prototype = {
- wash : function(){
- return this.bicycle.wash();
- },
- ride : function(){
- return this.bicycle.ride();
- },
- getPrice : function(){
- return this.bicycle.ride();
- }
- }
然後麼很巧妙的使用extend
- var BicycleBell = function( bicycle ){
- // 繼承 BicycleDecorator 內部 this定義的資料或者方法
- BicycleBell.superclass.constructor.call( this, bicycle );
- }
- // 繼承 BicycleDecorator.prototype 並且添加 BicycleBell.superclass 指向 BicycleDecorator.prototype
- extend( BicycleBell, BicycleDecorator );
- // 添加或者修改
- BicycleBell.prototype.bell = function(){
- console.log("ding! ding! ding!");
- }
- BicycleBell.prototype.getPrice = function(){
- return this.bicycle.getPrice() + 100;
- }
一樣的方式進行執行個體封裝使用
- var bicycleA = new ABicycle();
- bicycleA = new BicycleBell( bicycleA );
上述方式就是一種較為不錯的裝飾者模式形式,更完善一些可能需要對BicycleDecorator,以及BicycleDecorator.prototype對象定義時使用對最初類遍曆方式來擷取各個原有介面。
當然對於這幾種方式可以按需進行使用,有針對性的處理才是最有方案。