在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ 裝飾模式
中介者模式
MVVM
亨元模式
設(shè)計(jì)模式分類概覽表
ES Harmony
組合模式
CommonJS
jQuery 插件的設(shè)計(jì)模式
外觀模式
觀察者模式
建造者模式
構(gòu)造器模式
外觀模式
簡(jiǎn)介
AMD
原型模式
設(shè)計(jì)模式的分類
觀察者模式
命名空間模式
代理模式
編寫設(shè)計(jì)模式
適配器模式
反模式
什么是設(shè)計(jì)模式
模塊化模式
MVC
Mixin 模式
裝飾模式
設(shè)計(jì)模式的結(jié)構(gòu)
單例模式
迭代器模式
命令模式
工廠模式
MVP
暴露模塊模式
惰性初始模式

裝飾模式

裝飾器是旨在提升重用性能的一種結(jié)構(gòu)性設(shè)計(jì)模式。同Mixin類似,它可以被看作是應(yīng)用子類劃分的另外一種有價(jià)值的可選方案。

典型的裝飾器提供了向一個(gè)系統(tǒng)中現(xiàn)有的類動(dòng)態(tài)添加行為的能力。其創(chuàng)意是裝飾本身并不關(guān)心類的基礎(chǔ)功能,而只是將它自身拷貝到超類之中。

它們能夠被用來(lái)在不需要深度改變使用它們的對(duì)象的依賴代碼的前提下,變更我們希望向其中附加功能的現(xiàn)有系統(tǒng)之中。開(kāi)發(fā)者使用它們的一個(gè)通常的理由是,它們的應(yīng)用程序也許包含了需要大量彼此不相干類型對(duì)象的特性。想象一下不得不要去定義上百個(gè)不同對(duì)象的構(gòu)造器,比方說(shuō),一個(gè)Javascript游戲。

對(duì)象構(gòu)造器可以代表不同播放器類型,每一種類型具有不同的功能。一種叫做領(lǐng)主戒指的游戲會(huì)需要霍比特人、巫術(shù)師,獸人,巨獸,精靈,山嶺巨人,亂世陸地等對(duì)象的構(gòu)造器,而這些的數(shù)量很容易過(guò)百。而我們還要考慮為每一個(gè)類型的能力組合創(chuàng)建子類。

例如,帶指環(huán)的霍比特人,帶劍的霍比特人和插滿寶劍的陸地等等。這并不是非常的實(shí)用,當(dāng)我們考慮到不同能力的數(shù)量在不斷增長(zhǎng)這一因素時(shí),最后肯定是不可控的。

裝飾器模式并不去深入依賴于對(duì)象是如何創(chuàng)建的,而是專注于擴(kuò)展它們的功能這一問(wèn)題上。不同于只依賴于原型繼承,我們?cè)谝粋€(gè)簡(jiǎn)單的基礎(chǔ)對(duì)象上面逐步添加能夠提供附加功能的裝飾對(duì)象。它的想法是,不同于子類劃分,我們向一個(gè)基礎(chǔ)對(duì)象添加(裝飾)屬性或者方法,因此它會(huì)是更加輕巧的。

向Javascript中的對(duì)象添加新的屬性是一個(gè)非常直接了當(dāng)?shù)倪^(guò)程,因此將這一特定牢記于心,一個(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)是實(shí)用的,但它沒(méi)有真正展示出裝飾能夠貢獻(xiàn)出來(lái)的全部潛能。為這個(gè),我們首先區(qū)分一下我的Coffee示例和Freeman,Sierra和Bates所著Head First Design Patterns這一本優(yōu)秀的書中圍繞Mackbook商店建立的模型,這兩個(gè)之間的不同。

示例2:帶有多個(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() );

在上面的示例中,我們的裝飾器重載了超類對(duì)象MacBook()的 object.cost()函數(shù),使其返回的Macbook的當(dāng)前價(jià)格加上了被定制后升級(jí)的價(jià)格。

這被看做是對(duì)原來(lái)的Macbook對(duì)象構(gòu)造器方法的裝飾,它并沒(méi)有將其重寫(例如,screenSize()),我們所定義的Macbook的其它屬性也保持不變,完好無(wú)缺。

上面的示例并沒(méi)有真正定義什么接口,而且我們也轉(zhuǎn)移了從創(chuàng)造者到接受者移動(dòng)時(shí)確保一個(gè)對(duì)象對(duì)應(yīng)一個(gè)接口的責(zé)任。

偽古典裝飾器

我們現(xiàn)在要來(lái)試試首見(jiàn)于Dustin Diaz與Ross Harmes合著的Pro Javascript Design Patterns(PJDP)中一種裝飾器的變體。

不像早些時(shí)候的一些實(shí)例,Diaz和Harms堅(jiān)持更加近似于其他編程語(yǔ)言(如Java或者C++)如何使用一種“接口”的概念來(lái)實(shí)現(xiàn)裝飾器,我們不久就將對(duì)此進(jìn)行詳細(xì)的定義。

注意:裝飾模式的這一特殊變體是提供出來(lái)做參考用的。如果發(fā)現(xiàn)它過(guò)于復(fù)雜,建議你選擇前面更加簡(jiǎn)單的實(shí)現(xiàn)。

接口

PJDP所描述的裝飾器是一種被用于將具備相同接口的對(duì)象進(jìn)行透明封裝的對(duì)象,這樣一種模式。接口是一種定義一個(gè)對(duì)象應(yīng)該具有哪些方法的途徑,然而,它實(shí)際上并不指定那些方法應(yīng)該如何實(shí)現(xiàn)。

它們也可以聲明方法應(yīng)該有些什么參數(shù),但這被看做是可選項(xiàng)。

因此,為什么我們要在Javascript中使用接口呢?這個(gè)想法意在讓它們具有自說(shuō)明文檔特性,并促進(jìn)其重用性。在理論上,接口通過(guò)確保了其被改變的同時(shí)也要讓其對(duì)象實(shí)現(xiàn)這些改變,從而使得代碼更加的穩(wěn)定。

下面是一個(gè)在Javascript中使用鴨式類型來(lái)實(shí)現(xiàn)接口的示例,鴨式類型是一種基于所實(shí)現(xiàn)的方法來(lái)幫助判定一個(gè)對(duì)象是否是一種構(gòu)造器/對(duì)象的實(shí)體的方法。

// 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 = 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

在上面的代碼中,接口確保了實(shí)現(xiàn)提供嚴(yán)格的功能檢查,而這個(gè)和接口構(gòu)造器的接口代碼能在這里找到。

使用接口最大的問(wèn)題是,由于這并不是Javascript內(nèi)置的對(duì)它們的支持,對(duì)我們而言就會(huì)存在嘗試去模仿另外一種語(yǔ)言的特性,但看著并不完全合適,這樣一種風(fēng)險(xiǎn)。然而對(duì)于沒(méi)有太大性能消耗的輕量級(jí)接口是可以被使用的,并且下面我們將要看到的抽象裝飾器同樣使用了這個(gè)概念。

抽象裝飾者

為了闡明這個(gè)版本的裝飾者模式的結(jié)構(gòu),我們想象有一個(gè)超級(jí)類,還是一個(gè)Macbook模型,以及一個(gè)store,使我們可以用耗費(fèi)額外費(fèi)用的許多種增強(qiáng)來(lái)“裝飾”Macbook。

增強(qiáng)可以包括升級(jí)到4GB或8GB的Ram,雕刻,或相似案例。如果現(xiàn)在我們要針對(duì)每一種增強(qiáng)選項(xiàng)的組合,使用單獨(dú)的子類進(jìn)行建模,可能看起來(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í)際的解決方案,因?yàn)橐粋€(gè)新的子類可能需要具有每一種可能的增強(qiáng)組合。由于我們傾向于保持事物簡(jiǎn)單,不想維持一個(gè)巨大的子類集合,我們來(lái)看看怎樣用裝飾者更好的解決這個(gè)問(wèn)題。

不需要我們前面看到的所有組合,我們只需要簡(jiǎn)單的創(chuàng)建五個(gè)新的裝飾者類。對(duì)這些增強(qiáng)類的方法調(diào)用,將會(huì)傳遞給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),一種帶有被用來(lái)實(shí)現(xiàn)Mackbook接口的默認(rèn)方法的抽象裝飾器方法被定義了出來(lái),其剩余的選項(xiàng)將會(huì)進(jìn)行子類劃分。抽象裝飾器確保了我們能夠獨(dú)立于盡可能多的在不同的組合中所需的裝飾器,去裝飾一個(gè)基礎(chǔ)類(記得早先的那個(gè)示例么?),而不需要去為了每一種可能的組合而去驅(qū)動(dòng)一個(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();
    }       
};

上述示例中所發(fā)生的是Macbook裝飾器在像組件一樣的使用一個(gè)對(duì)象。它使用了我們?cè)缦榷x的Macbook接口,對(duì)于每一個(gè)方法都調(diào)用了組件上相同的方法。我們現(xiàn)在就能夠只使用Macbook裝飾器來(lái)創(chuàng)建我們的選項(xiàng)類了——通過(guò)簡(jiǎn)單調(diào)用超類的構(gòu)造器和根據(jù)需要可以被重載的方法。

var CaseDecorator = function( macbook ){

   // call the superclass's constructor next
   this.superclass.constructor( macbook );  

};

// Let's now extend the superclass
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; 
};

如我們所見(jiàn),大多數(shù)都是相對(duì)應(yīng)的直接實(shí)現(xiàn)。我們所做的是重載需要被裝飾的addCase()和getPrise()方法,而我們通過(guò)首先執(zhí)行組件的方法然后將其添加到它里面,來(lái)達(dá)到目的。

鑒于到目前為止本節(jié)所介紹的信息一斤相當(dāng)?shù)亩嗔耍屛覀冊(cè)囋噷⑵淙糠诺揭粋€(gè)單獨(dú)的實(shí)例中,以期突出我們所學(xué)。

// Instantiation of the macbook
var myMacbookPro = new MacbookPro(); 

// Outputs: 900.00
console.log( myMacbookPro.getPrice() );

// Decorate the macbook
myMacbookPro = new CaseDecorator( myMacbookPro );

// This will return 945.00
console.log( myMacbookPro.getPrice() );

由于裝飾器能夠動(dòng)態(tài)的修改對(duì)象,它們就是改變現(xiàn)有系統(tǒng)的理想模式。有時(shí)候,它只是簡(jiǎn)單的圍繞一個(gè)對(duì)象及其維護(hù)針對(duì)每一個(gè)對(duì)象類型單獨(dú)的子類劃分所產(chǎn)生的麻煩,來(lái)創(chuàng)建裝飾器的。這使得維護(hù)起可能需要大量子類劃分對(duì)象的應(yīng)用程序來(lái)更加顯著的直接。

裝飾器和jQuery

同我們所涵蓋的其它模式一起,也有許多裝飾器模式的示例能夠使用jQuery來(lái)實(shí)現(xiàn)。jQuery.extend()允許我們將兩個(gè)或者更多個(gè)對(duì)象(以及它們的屬性)擴(kuò)展(或者混合)到一個(gè)對(duì)象中,不論是在運(yùn)行時(shí)或者動(dòng)態(tài)的在一個(gè)稍后的時(shí)點(diǎn)上。

在這一場(chǎng)景中,目標(biāo)對(duì)象沒(méi)必要打斷或者重載源/超類中現(xiàn)有的方法(盡管這可以被做到)就能夠使用新的功能裝飾起來(lái)。 在接下來(lái)的示例中,我們定義了三個(gè)對(duì)象:默認(rèn),選項(xiàng)和設(shè)置。任務(wù)的目標(biāo)是用在選項(xiàng)中找到的附加功能來(lái)裝飾默認(rèn)對(duì)象。

  • 將“默認(rèn)”放置在一個(gè)不可觸及的狀態(tài)之中,在這里我們不會(huì)失去訪問(wèn)稍后會(huì)在其中發(fā)現(xiàn)的屬性和方法的能力
  • 贏得了使用在“選項(xiàng)”中找到被裝飾起來(lái)的屬性和函數(shù)的能力。
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!"); } }
// options -- { validate: true, name: bar, helloWorld: function (){ console.log("hello!"); } }
// defaults -- { validate: false, limit: 5, name: foo, welcome: function (){ console.log("welcome!"); } }

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

因?yàn)樗梢员煌该鞯氖褂茫⑶乙蚕喈?dāng)?shù)撵`活,因此開(kāi)發(fā)者都挺樂(lè)意去使用這個(gè)模式——如我們所見(jiàn),對(duì)象可以用新的行為封裝或者“裝飾”起來(lái),而后繼續(xù)使用,并不用去擔(dān)心基礎(chǔ)的對(duì)象被改變。在一個(gè)更加廣泛的范圍內(nèi),這一模式也避免了我們?nèi)ヒ蕾嚧罅孔宇悂?lái)實(shí)現(xiàn)同樣的效果。

然而在實(shí)現(xiàn)這個(gè)模式時(shí),也存在我們應(yīng)該意識(shí)到的缺點(diǎn)。如果窮于管理,它也會(huì)由于引入了許多微小但是相似的對(duì)象到我們的命名空間中,從而顯著的使得我們的應(yīng)用程序架構(gòu)變得復(fù)雜起來(lái)。這里所擔(dān)憂的是,除了漸漸變得難于管理,其他不能熟練使用這個(gè)模式的開(kāi)發(fā)者也可能會(huì)有一段要掌握它被使用的理由的艱難時(shí)期。

足夠的注釋或者對(duì)模式的研究,對(duì)此應(yīng)該有助益,而只要我們對(duì)在我們的應(yīng)程序中的多大范圍內(nèi)使用這一模式有所掌控的話,我們就能讓兩方面都得到改善。

上一篇:惰性初始模式下一篇:命令模式