裝飾器模式

裝飾器模式

裝飾器模式是一種旨在提升代碼復(fù)用率的結(jié)構(gòu)性模式。有點(diǎn)類似于混入模式,它被認(rèn)為是一種可以替代子類的可行方案。

一般來(lái)說(shuō),裝飾器提供一種動(dòng)態(tài)的為系統(tǒng)中的類添加行為的能力。裝飾器本身沒(méi)必要有類的基本功能,否則它自己就可以作為父類了。

他們通??梢员挥脕?lái)修改現(xiàn)存的系統(tǒng),為一些對(duì)象加入一些額外的功能(特性),而不用大量修改底層代碼。開(kāi)發(fā)人員使用他們的一個(gè)常見(jiàn)原因可能包含特征——需要大量不同類型的對(duì)象。想象一下要定義幾百種不同的對(duì)象構(gòu)造函數(shù),比如一個(gè)js游戲。

對(duì)象構(gòu)造函數(shù)可能表示不同類型的角色類型,每個(gè)角色都有不同的能力。魔戒這款游戲可能需要霍比特人,精靈,獸人,精靈,山嶺巨人,石巨人等等,這些很容易有成百上千個(gè)。如果我們?cè)倏紤]能力,想象得為每種能力的結(jié)合創(chuàng)建一個(gè)子類,例如帶指環(huán)的霍比特人,帶劍的霍比特人,帶指環(huán)和劍的霍比特人等等。當(dāng)我們考慮伴隨著能力類型數(shù)量的增長(zhǎng),這是非常不實(shí)用且不可想象的。

裝飾器模式?jīng)]有過(guò)分的關(guān)注如何對(duì)象如何被創(chuàng)建,而是如何擴(kuò)展他們的功能。相比較原型繼承,我們用一個(gè)單一的基本對(duì)象,并逐步第為它增加提供額外能力的對(duì)象的方式工作。這個(gè)想法相比較子類,通過(guò)增加一些屬性或者方法到基本對(duì)象上,可以更加精簡(jiǎn)。

為js對(duì)像添加新屬性是一個(gè)非常直接的過(guò)程,考慮著一點(diǎn),一個(gè)非常簡(jiǎn)單的裝飾器可能實(shí)現(xiàn)如下:

案例1:具有新功能的裝飾構(gòu)造器

// A vehicle constructor
function Vehicle( vehicleType ){
 
    // some sane defaults
    this.vehicleType = vehicleType || "car";
    this.model = "default";
    this.license = "00000-000";
 
}
 
// Test instance for a basic vehicle
var testInstance = new Vehicle( "car" );
console.log( testInstance );
 
// Outputs:
// vehicle: car, model:default, license: 00000-000
 
// Lets create a new instance of vehicle, to be decorated
var truck = new Vehicle( "truck" );
 
// New functionality we're decorating vehicle with
truck.setModel = function( modelName ){
    this.model = modelName;
};
 
truck.setColor = function( color ){
    this.color = color;
};
 
// Test the value setters and value assignment works correctly
truck.setModel( "CAT" );
truck.setColor( "blue" );
 
console.log( truck );
 
// Outputs:
// vehicle:truck, model:CAT, color: blue
 
// Demonstrate "vehicle" is still unaltered
var secondInstance = new Vehicle( "car" );
console.log( secondInstance );
 
// Outputs:
// vehicle: car, model:default, license: 00000-000

這種最簡(jiǎn)單的實(shí)現(xiàn)是有多種用途的,但是并沒(méi)有真正的展示裝飾器提供的全部力量。對(duì)于這一點(diǎn),我們將首先完成一個(gè)優(yōu)秀的案例。

案例2:用多個(gè)裝飾器裝飾一個(gè)對(duì)象

// The constructor to decorate
function MacBook() {
 
  this.cost = function () { return 997; };
  this.screenSize = function () { return 11.6; };
 
}
 
// Decorator 1
function memory( macbook ) {
 
  var v = macbook.cost();
  macbook.cost = function() {
    return v + 75;
  };
 
}
 
// Decorator 2
function engraving( macbook ){
 
  var v = macbook.cost();
  macbook.cost = function(){
    return v + 200;
  };
 
}
 
// Decorator 3
function insurance( macbook ){
 
  var v = macbook.cost();
  macbook.cost = function(){
     return v + 250;
  };
 
}
 
var mb = new MacBook();
memory( mb );
engraving( mb );
insurance( mb );
 
// Outputs: 1522
console.log( mb.cost() );
 
// Outputs: 11.6
console.log( mb.screenSize() );

In the above example, our Decorators are overriding the MacBook() super-class objects .cost() function to return the current price of the Macbook plus the cost of the upgrade being specified.

在上面的例子中,我們就要重寫(xiě)了macbook()父類對(duì)象的cost()函數(shù)以返回的MacBook升級(jí)特定部分后加總的目前價(jià)格。

裝飾器只修改原對(duì)象的部分。

偽-經(jīng)典 裝飾器

這種裝飾器模式的特別變種提供參考用途,如果發(fā)現(xiàn)它太過(guò)復(fù)雜,我建議選擇一個(gè)之前提到過(guò)的簡(jiǎn)單實(shí)現(xiàn)。

接口

PJDP 描述裝飾器作為一種模式被用來(lái),將對(duì)象透明的包裝在其他具有相同接口中的對(duì)象中。接口是一種一個(gè)對(duì)象的方法應(yīng)該做什么的定義方式。然而,他并不指明如何實(shí)現(xiàn)這些方法。

他們可以指明方法所使用的參數(shù),但這被認(rèn)為是可選的。

那么,我們?yōu)槭裁匆趈s中使用接口呢?我們的像是,接口自身就是種記錄,并且可以提高復(fù)用性。理論上說(shuō),接口也使得代碼更穩(wěn)定,通過(guò)確保改變他們也必須改變實(shí)現(xiàn)他們的對(duì)象。

以下是一種通過(guò)js鴨式編程(一種方法,幫助決定一個(gè)對(duì)象是否是一個(gè)構(gòu)造函數(shù)/基于構(gòu)造函數(shù)生成對(duì)象的實(shí)例,的實(shí)現(xiàn))方式實(shí)現(xiàn)的接口的例子。

// Create interfaces using a pre-defined Interface
// constructor that accepts an interface name and
// skeleton methods to expose.
 
// In our reminder example summary() and placeOrder()
// represent functionality the interface should
// support
var reminder = new Interface( "List", ["summary", "placeOrder"] );
 
var properties = {
  name: "Remember to buy the milk",
  date: "05/06/2016",
  actions:{
    summary: function (){
      return "Remember to buy the milk, we are almost out!";
   },
    placeOrder: function (){
      return "Ordering milk from your local grocery store";
    }
  }
};
 
// Now create a constructor implementing the above properties
// and methods
 
function Todo( config ){
 
  // State the methods we expect to be supported
  // as well as the Interface instance being checked
  // against
 
  Interface.ensureImplements( config.actions, reminder );
 
  this.name = config.name;
  this.methods = config.actions;
 
}
 
// Create a new instance of our Todo constructor
 
var todoItem = new Todo( properties );
 
// Finally test to make sure these function correctly
 
console.log( todoItem.methods.summary() );
console.log( todoItem.methods.placeOrder() );
 
// Outputs:
// Remember to buy the milk, we are almost out!
// Ordering milk from your local grocery store

上面的代碼中,Interface.ensureImplements確保嚴(yán)格的功能檢查,關(guān)于Interface實(shí)現(xiàn)可以看這里:[URL:https://gist.github.com/1057989]

使用接口最大的問(wèn)題是,js并沒(méi)提供內(nèi)置的接口支持,當(dāng)我們?cè)噲D模擬其他語(yǔ)言的特性的有可能是不合適的。清理的接口可能不會(huì)造成太大的性能開(kāi)銷,我們接下來(lái)將看一下使用相同概念的抽象裝飾器模式。

抽象裝飾器

為了展示這個(gè)版本的的裝飾模式,我們準(zhǔn)備再次想象有個(gè)父類模型Macbook,和一個(gè)允許我們付費(fèi)升級(jí)我們Macbook的商店。

升級(jí)可以包括,4GB Ram到8GB Ram,雕刻或者其他。如果我們的模型使用獨(dú)立的子類組合各種可能的升級(jí)選項(xiàng),那么將看起來(lái)像這樣:

var Macbook = function(){
        //...
};
 
var  MacbookWith4GBRam = function(){},
     MacbookWith8GBRam = function(){},
     MacbookWith4GBRamAndEngraving = function(){},
     MacbookWith8GBRamAndEngraving = function(){},
     MacbookWith8GBRamAndParallels = function(){},
     MacbookWith4GBRamAndParallels = function(){},
     MacbookWith8GBRamAndParallelsAndCase = function(){},
     MacbookWith4GBRamAndParallelsAndCase = function(){},
     MacbookWith8GBRamAndParallelsAndCaseAndInsurance = function(){},
     MacbookWith4GBRamAndParallelsAndCaseAndInsurance = function(){};

這是一個(gè)不切實(shí)際的解決方案,作為一個(gè)新的子類將需要每一個(gè)可能的組合。因?yàn)槲覀兿矚g將事情簡(jiǎn)單化而不是維護(hù)一堆的子類,讓我們看看裝飾器模式如何更好的幫我們解決這個(gè)問(wèn)題。

相比要求我們?cè)缙诳吹降娜拷M合,我們只需要?jiǎng)?chuàng)建五個(gè)裝飾器類。在這些加強(qiáng)類上調(diào)用的方法將被傳遞給我們的Macbook類。

在下一個(gè)例子中,裝飾器透明的包裝他們的組件,并且有趣的是可以使用相同的接口互換他們。

這是我們將定義的Macbook接口:

var Macbook = new Interface( "Macbook",
  ["addEngraving",
  "addParallels",
  "add4GBRam",
  "add8GBRam",
  "addCase"]);
 
// A Macbook Pro might thus be represented as follows:
var MacbookPro = function(){
    // implements Macbook
};
 
MacbookPro.prototype = {
    addEngraving: function(){
    },
    addParallels: function(){
    },
    add4GBRam: function(){
    },
    add8GBRam:function(){
    },
    addCase: function(){
    },
    getPrice: function(){
      // Base price
      return 900.00;
    }
};

為了使以后我們更容易的添加更多的選項(xiàng),一個(gè)抽象抽象的裝飾器被定義,并且需要默認(rèn)實(shí)現(xiàn)Macbook類所定義的接口,剩余的選項(xiàng)講需要子類。抽象裝飾器確保我們可以裝飾一個(gè)基本類獨(dú)立于為了不同的組合而存在許多裝飾器,不需要為每一可能的組合派生出一個(gè)類。

// Macbook decorator abstract decorator class
 
var MacbookDecorator = function( macbook ){
 
    Interface.ensureImplements( macbook, Macbook );
    this.macbook = macbook;
 
};
 
MacbookDecorator.prototype = {
    addEngraving: function(){
        return this.macbook.addEngraving();
    },
    addParallels: function(){
        return this.macbook.addParallels();
    },
    add4GBRam: function(){
        return this.macbook.add4GBRam();
    },
    add8GBRam:function(){
        return this.macbook.add8GBRam();
    },
    addCase: function(){
        return this.macbook.addCase();
    },
    getPrice: function(){
        return this.macbook.getPrice();
    }
};

上面的示例中Macbook裝飾器接受一個(gè)對(duì)象作為我們的基本組件,它使用我們之前定義的Macbook接口并且每個(gè)方法只需要調(diào)用組件上相同的方法。我們現(xiàn)在可以創(chuàng)建我們的選項(xiàng)類,用來(lái)裝飾Macbook。


// First, define a way to extend an object a
// with the properties in object b. We'll use
// this shortly!
function extend( a, b ){
    for( var key in b )
        if( b.hasOwnProperty(key) )
            a[key] = b[key];
    return a;
}
 
var CaseDecorator = function( macbook ){
   this.macbook = macbook;
};
 
// Let's now extend (decorate) the CaseDecorator
// with a MacbookDecorator
extend( CaseDecorator, MacbookDecorator );
 
CaseDecorator.prototype.addCase = function(){
    return this.macbook.addCase() + "Adding case to macbook";
};
 
CaseDecorator.prototype.getPrice = function(){
    return this.macbook.getPrice() + 45.00;
};

我們?cè)谶@里重寫(xiě)了需要被裝飾的addCase()和getPrice()方法,并且我們實(shí)現(xiàn)它是通過(guò)首先在原始的Macbook對(duì)象中調(diào)用它。并簡(jiǎn)單的加一個(gè)字符串或者一個(gè)數(shù)字值。

到目前為止有大量的信息被展現(xiàn)在本章節(jié)。讓我們?cè)囍靡粋€(gè)簡(jiǎn)單的案例總結(jié)在一起,這樣有望于突出我們所學(xué)。

// Instantiation of the macbook
var myMacbookPro = new MacbookPro();
 
// Outputs: 900.00
console.log( myMacbookPro.getPrice() );
 
// Decorate the macbook
var decoratedMacbookPro = new CaseDecorator( myMacbookPro );
 
// This will return 945.00
console.log( decoratedMacbookPro.getPrice() );

作為裝飾器可以動(dòng)態(tài)的修改對(duì)象,這對(duì)于修改現(xiàn)存系統(tǒng)是一個(gè)非常棒的模式。有時(shí),他只是簡(jiǎn)單的額創(chuàng)建一個(gè)相關(guān)的對(duì)象,以對(duì)抗為每個(gè)對(duì)象類型維護(hù)一堆子類。這使得需要維護(hù)一大堆子類對(duì)象的應(yīng)用變得更直接。

本例的功能版本可以在JSBin上找到。

jQuery中的裝飾器

正如我們上面所提到的其他模式一樣,這里也有一個(gè)裝飾模式的例子可以通過(guò)jQuery實(shí)現(xiàn)。jQuery.extend()允許我們?cè)诔绦蜻\(yùn)行中擴(kuò)展(或者合并)兩個(gè)或者更多個(gè)對(duì)象到一個(gè)對(duì)象中。

在這種情況下,一個(gè)目標(biāo)對(duì)象可以從原對(duì)象/父類,被裝飾新的功能而不用打破或者重寫(xiě)現(xiàn)有的方法。

在下面的案例中,我們定義了三個(gè)對(duì)象:defaults,options 和settings。我們的目標(biāo)是用在options,settings中的額外功能去裝飾默認(rèn)的對(duì)象。我們必須:

a)保留“default”的狀態(tài)在一種未接觸的狀態(tài)下,即我們不會(huì)在之后的時(shí)間點(diǎn)失去訪問(wèn)它屬性和方法的權(quán)限。
b)獲得options中用來(lái)裝飾屬性和功能的能力。

var decoratorApp = decoratorApp || {};
 
// define the objects we're going to use
decoratorApp = {
 
    defaults: {
        validate: false,
        limit: 5,
        name: "foo",
        welcome: function () {
            console.log( "welcome!" );
        }
    },
 
    options: {
        validate: true,
        name: "bar",
        helloWorld: function () {
            console.log( "hello world" );
        }
    },
 
    settings: {},
 
    printObj: function ( obj ) {
        var arr = [],
            next;
        $.each( obj, function ( key, val ) {
            next = key + ": ";
            next += $.isPlainObject(val) ? printObj( val ) : val;
            arr.push( next );
        } );
 
        return "{ " + arr.join(", ") + " }";
    }
 
};
 
// merge defaults and options, without modifying defaults explicitly
decoratorApp.settings = $.extend({}, decoratorApp.defaults, decoratorApp.options);
 
// what we have done here is decorated defaults in a way that provides
// access to the properties and functionality it has to offer (as well as
// that of the decorator "options"). defaults itself is left unchanged
 
$("#log")
    .append( decoratorApp.printObj(decoratorApp.settings) +
          + decoratorApp.printObj(decoratorApp.options) +
          + decoratorApp.printObj(decoratorApp.defaults));
 
// settings -- { validate: true, limit: 5, name: bar, welcome: function (){ console.log( "welcome!" ); },
// helloWorld: function (){ console.log( "hello world" ); } }
// options -- { validate: true, name: bar, helloWorld: function (){ console.log( "hello world" ); } }
// defaults -- { validate: false, limit: 5, name: foo, welcome: function (){ console.log("welcome!"); } }
 

優(yōu)點(diǎn)和缺點(diǎn)

開(kāi)發(fā)人員喜歡使用這種模式,因?yàn)樗梢员煌该鞯氖褂谩2⑶乙彩肿杂?,正如我們看到的那樣,?duì)象可以被包裹或者裝飾而含有新的行為,繼續(xù)被使用并且不用擔(dān)心原來(lái)對(duì)象被修改。在更管飯的上下文中,這種模式也可以避免我們需要依賴一大子類而獲得同樣的效用。

當(dāng)然實(shí)現(xiàn)這種模式的時(shí)候我們也應(yīng)該意識(shí)到一些缺點(diǎn)。如果管理不善,它可以顯著的復(fù)雜話我們的應(yīng)用架構(gòu),因?yàn)樗o我們的命名空間進(jìn)入了許多小但是相似的對(duì)象。除了變得難以管理,其他不熟悉這種模式的開(kāi)發(fā)者可能很難了解為什么這種模式被使用。

充足的交流和模式的研究可以對(duì)第二個(gè)問(wèn)題有幫助,然而,我們應(yīng)該控制我們的應(yīng)用程序中裝飾器的廣泛成都,并且統(tǒng)計(jì)他們的全部數(shù)量。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 定義 裝飾器模式又名包裝(Wrapper)模式。裝飾器模式以對(duì)客戶端透明的方式拓展對(duì)象的功能,是繼承關(guān)系的一種替代...
    步積閱讀 36,589評(píng)論 0 38
  • 裝飾器模式可以在不修改代碼的情況下靈活的為一對(duì)象添加行為和職責(zé)。當(dāng)你要修改一個(gè)被其它類包含的類的行為時(shí),它可以代替...
    泥孩兒0107閱讀 352評(píng)論 0 0
  • 前言 距離上一篇,間隔時(shí)間有點(diǎn)長(zhǎng)哈(尷尬 ==!)經(jīng)歷過(guò)漫長(zhǎng)的實(shí)習(xí)期,試用期,第一份工作終于慢慢走上正軌,中間發(fā)生...
    暗影飛客閱讀 1,265評(píng)論 0 0
  • 概述 裝飾器模式一種動(dòng)態(tài)地往一個(gè)類中添加新的行為的設(shè)計(jì)模式。就功能而言,修飾模式相比生成子類更為靈活,這樣可以給某...
    ymjkMaster閱讀 814評(píng)論 0 0
  • 1 策略模式:定義一系列算法的方法,所有的算法功能相同但是實(shí)現(xiàn)不同。 示例類圖: 如上類圖所示:鴨子有兩個(gè)可能...
    Richard_80ec閱讀 3,432評(píng)論 0 2

友情鏈接更多精彩內(nèi)容