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

鍍金池/ 教程/ HTML/ 設(shè)計(jì)模式之組合模式
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫(xiě)高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒(méi)有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開(kāi)閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

設(shè)計(jì)模式之組合模式

介紹

組合模式(Composite)將對(duì)象組合成樹(shù)形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu),組合模式使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性。

常見(jiàn)的場(chǎng)景有 asp.net 里的控件機(jī)制(即 control 里可以包含子 control,可以遞歸操作、添加、刪除子 control),類似的還有 DOM 的機(jī)制,一個(gè) DOM 節(jié)點(diǎn)可以包含子節(jié)點(diǎn),不管是父節(jié)點(diǎn)還是子節(jié)點(diǎn)都有添加、刪除、遍歷子節(jié)點(diǎn)的通用功能。所以說(shuō)組合模式的關(guān)鍵是要有一個(gè)抽象類,它既可以表示子元素,又可以表示父元素。

正文

舉個(gè)例子,有家餐廳提供了各種各樣的菜品,每個(gè)餐桌都有一本菜單,菜單上列出了該餐廳所偶的菜品,有早餐糕點(diǎn)、午餐、晚餐等等,每個(gè)餐都有各種各樣的菜單項(xiàng),假設(shè)不管是菜單項(xiàng)還是整個(gè)菜單都應(yīng)該是可以打印的,而且可以添加子項(xiàng),比如午餐可以添加新菜品,而菜單項(xiàng)咖啡也可以添加糖啊什么的。

這種情況,我們就可以利用組合的方式將這些內(nèi)容表示為層次結(jié)構(gòu)了。我們來(lái)逐一分解一下我們的實(shí)現(xiàn)步驟。

第一步,先實(shí)現(xiàn)我們的“抽象類”函數(shù) MenuComponent

var MenuComponent = function () {
};
MenuComponent.prototype.getName = function () {
    throw new Error("該方法必須重寫(xiě)!");
};
MenuComponent.prototype.getDescription = function () {
    throw new Error("該方法必須重寫(xiě)!");
};
MenuComponent.prototype.getPrice = function () {
    throw new Error("該方法必須重寫(xiě)!");
};
MenuComponent.prototype.isVegetarian = function () {
    throw new Error("該方法必須重寫(xiě)!");
};
MenuComponent.prototype.print = function () {
    throw new Error("該方法必須重寫(xiě)!");
};
MenuComponent.prototype.add = function () {
    throw new Error("該方法必須重寫(xiě)!");
};
MenuComponent.prototype.remove = function () {
    throw new Error("該方法必須重寫(xiě)!");
};
MenuComponent.prototype.getChild = function () {
    throw new Error("該方法必須重寫(xiě)!");
};

該函數(shù)提供了 2 種類型的方法,一種是獲取信息的,比如價(jià)格,名稱等,另外一種是通用操作方法,比如打印、添加、刪除、獲取子菜單。

第二步,創(chuàng)建基本的菜品項(xiàng)

var MenuItem = function (sName, sDescription, bVegetarian, nPrice) {
    MenuComponent.apply(this);
    this.sName = sName;
    this.sDescription = sDescription;
    this.bVegetarian = bVegetarian;
    this.nPrice = nPrice;
};
MenuItem.prototype = new MenuComponent();
MenuItem.prototype.getName = function () {
    return this.sName;
};
MenuItem.prototype.getDescription = function () {
    return this.sDescription;
};
MenuItem.prototype.getPrice = function () {
    return this.nPrice;
};
MenuItem.prototype.isVegetarian = function () {
    return this.bVegetarian;
};
MenuItem.prototype.print = function () {
    console.log(this.getName() + ": " + this.getDescription() + ", " + this.getPrice() + "euros");
};

由代碼可以看出,我們只重新了原型的 4 個(gè)獲取信息的方法和 print 方法,沒(méi)有重載其它3個(gè)操作方法,因?yàn)榛静似凡话砑印h除、獲取子菜品的方式。

第三步,創(chuàng)建菜品

var Menu = function (sName, sDescription) {
    MenuComponent.apply(this);
    this.aMenuComponents = [];
    this.sName = sName;
    this.sDescription = sDescription;
    this.createIterator = function () {
        throw new Error("This method must be overwritten!");
    };
};
Menu.prototype = new MenuComponent();
Menu.prototype.add = function (oMenuComponent) {
    // 添加子菜品
    this.aMenuComponents.push(oMenuComponent);
};
Menu.prototype.remove = function (oMenuComponent) {
    // 刪除子菜品
    var aMenuItems = [];
    var nMenuItem = 0;
    var nLenMenuItems = this.aMenuComponents.length;
    var oItem = null;
    for (; nMenuItem < nLenMenuItems; ) {
        oItem = this.aMenuComponents[nMenuItem];
        if (oItem !== oMenuComponent) {
            aMenuItems.push(oItem);
        }
        nMenuItem = nMenuItem + 1;
    }
    this.aMenuComponents = aMenuItems;
};
Menu.prototype.getChild = function (nIndex) {
    //獲取指定的子菜品
    return this.aMenuComponents[nIndex];
};
Menu.prototype.getName = function () {
    return this.sName;
};
Menu.prototype.getDescription = function () {
    return this.sDescription;
};
Menu.prototype.print = function () {
    // 打印當(dāng)前菜品以及所有的子菜品
    console.log(this.getName() + ": " + this.getDescription());
    console.log("--------------------------------------------");
    var nMenuComponent = 0;
    var nLenMenuComponents = this.aMenuComponents.length;
    var oMenuComponent = null;
    for (; nMenuComponent < nLenMenuComponents; ) {
        oMenuComponent = this.aMenuComponents[nMenuComponent];
        oMenuComponent.print();
        nMenuComponent = nMenuComponent + 1;
    }
};

注意上述代碼,除了實(shí)現(xiàn)了添加、刪除、獲取方法外,打印 print 方法是首先打印當(dāng)前菜品信息,然后循環(huán)遍歷打印所有子菜品信息。

第四步,創(chuàng)建指定的菜品

我們可以創(chuàng)建幾個(gè)真實(shí)的菜品,比如晚餐、咖啡、糕點(diǎn)等等,其都是用 Menu 作為其原型,代碼如下:

var DinnerMenu = function () {
    Menu.apply(this);
};
DinnerMenu.prototype = new Menu();
var CafeMenu = function () {
    Menu.apply(this);
};
CafeMenu.prototype = new Menu();
var PancakeHouseMenu = function () {
    Menu.apply(this);
};
PancakeHouseMenu.prototype = new Menu();

第五步,創(chuàng)建最頂級(jí)的菜單容器——菜單本

var Mattress = function (aMenus) {
    this.aMenus = aMenus;
};
Mattress.prototype.printMenu = function () {
    this.aMenus.print();
};

該函數(shù)接收一個(gè)菜單數(shù)組作為參數(shù),并且值提供了 printMenu 方法用于打印所有的菜單內(nèi)容。

第六步,調(diào)用方式

var oPanCakeHouseMenu = new Menu("Pancake House Menu", "Breakfast");
var oDinnerMenu = new Menu("Dinner Menu", "Lunch");
var oCoffeeMenu = new Menu("Cafe Menu", "Dinner");
var oAllMenus = new Menu("ALL MENUS", "All menus combined");
oAllMenus.add(oPanCakeHouseMenu);
oAllMenus.add(oDinnerMenu);
oDinnerMenu.add(new MenuItem("Pasta", "Spaghetti with Marinara Sauce, and a slice of sourdough bread", true, 3.89));
oDinnerMenu.add(oCoffeeMenu);
oCoffeeMenu.add(new MenuItem("Express", "Coffee from machine", false, 0.99));
var oMattress = new Mattress(oAllMenus);
console.log("---------------------------------------------");
oMattress.printMenu();
console.log("---------------------------------------------");

熟悉 asp.net 控件開(kāi)發(fā)的同學(xué),是不是看起來(lái)很熟悉?

總結(jié)

組合模式的使用場(chǎng)景非常明確:

  1. 你想表示對(duì)象的部分-整體層次結(jié)構(gòu)時(shí);
  2. 你希望用戶忽略組合對(duì)象和單個(gè)對(duì)象的不同,用戶將統(tǒng)一地使用組合結(jié)構(gòu)中的所有對(duì)象(方法)

另外該模式經(jīng)常和裝飾者一起使用,它們通常有一個(gè)公共的父類(也就是原型),因此裝飾必須支持具有 add、remove、getChild 操作的 component 接口。