組合模式

一、定義

組合模式,將對象組合成樹形結構以表示“部分-整體”的層次結構,組合模式使得用戶對單個對象和組合對象的使用具有一致性

在定義中提到了“部分-整體”、“單個對象”、“組合對象”這幾個關鍵詞,因此掌握組合模式的重點是要理解清楚 “部分/整體” 還有 ”單個對象“ 與 “組合對象” 的含義

二、作用

組合模式的作用即定義描述的那樣,有兩個作用:

  1. 將對象組合成樹形結構,以表示“部分-整體”的層次結構
  2. 通過對象的多態性表現,使得用戶對單個對象和組合對象的使用具有一致性

針對以上的兩點,下面做一個詳細說明,首先看一段代碼:這是一個模擬電腦開機,運行應用程序的簡單宏命令例子

let runWeChat = {
    run : function() {
        console.log("wechat已經啓動")
    }
}
let runQQ = {
    run : function() {
        console.log("QQ已經啓動")
    }
}
let runChrome = {
    run : function() {
        console.log("Google Chrome已經啓動")
    }
}
let applicationCommand = function () {
    return {
        commandLists : [],
        addCommand: function (command) {
            this.commandLists.push(command)
        },
        run: function () {
            this.commandLists.map((item, index) => {
                item.run()
            })
        }
    }
}

let ac = applicationCommand ()

ac.addCommand(runWeChat)

ac.addCommand(runQQ)

ac.addCommand(runChrome)

ac.run()

代碼結構圖-2.png

  • 這樣樹形遍歷的結構,通過調用組合對象的run方法,程序便會遞歸調用組合對象下面的子對象的run方法,因此我們只要按下電腦的開機鍵,微信,qq,谷歌瀏覽器這些程序就會被順序運行。這樣的方式很簡潔的描述出了部分-整體的結構
  • 在這個組合模式中,利用對象的多態性表現,我們可以不關心組合對象和單個對象的區別,使用時統一使用組合結構中的所有對象,而不需要關心它是組合對象還是單個對象

這樣的方式在實際的開發中可以帶來很大的便利性。當我們在開機程序中添加命令時,無需關心這個是宏命令還是子命令,只關心這個命令是有可以執行的run方法,那麼這個命令就可以被添加。執行的差異是在代碼裏體現的,對客戶無感。

仍以上面的宏命令爲例,請求在樹最頂端的對象往下傳遞,如果此時處理請求的是普通子命令(葉子對象),這個時候葉子對象自己就會對請求做出處理;若此時處理請求的宏命令(組合對象),組合對象則遍歷它的子對象,將請求傳遞下去。上面的例子只是簡單的組合對象下只有葉子對象,葉子對象是樹的最小單位,不會再有其他子節點,組合對象下可能還有子節點,如下圖:

樹形圖-3.png

請求是由上至下沿着樹進行傳遞,直到葉子對象,作爲使用者,只需要關心樹最頂層的組合對象,只要請求這個組合對象,請求便會沿着樹往下傳遞,順次到達所有的葉子對象

三、組合模式例子-員工部門關係

在公司裏,必然存在員工的部門以及從屬關係(這裏只考慮每個人只屬於一個部門),公司分爲衆多的部門,每個部門會有一個主管,帶領下面的員工,這個員工可能只是最末端的一個職位,也有可能在他手下還有一波兄弟,每個部門是一個組合對象,部門下面的小組還是一個組合對象,這樣就給構成了組合的模式,接下來我們來看下例子:

let leaderStaff = function (name, sex, apartment) {
    this.name = name;
    this.sex = sex;
    this.apartment = apartment;
    this.children = [];
}
leaderStaff.prototype.add = function (child) {
    this.children.push(child)
}
leaderStaff.prototype.doCount = function () {
    console.log("leader", this.name +"--"+ this.sex +"--"+ this.apartment)
    this.children.map((child, index) => {
        child.doCount()
    })
}   
let staff = function(name, sex, apartment) {
    this.name = name;
    this.sex = sex;
    this.apartment = apartment;
}
staff.prototype.add = function() {
    throw new Error("我還只是個孩子!!->_->")
}
staff.prototype.doCount = function () {
    console.log("普通員工",this.name +"--"+ this.sex +"--"+ this.apartment)
}
let leaderStaff1 = new leaderStaff('大旺', '男', 'A')
let leaderStaff2 = new leaderStaff('大張', '男', 'B')
let leaderStaff3 = new leaderStaff('大李', '男', 'C')
let leaderStaff4 = new leaderStaff('大無', '女', 'D')
let staff5 = new staff('小馬', '', '0') 
let staff1 = new staff('小黑', '男', 'd')
let staff2 = new staff('小撒', '女', 'e')
let staff3 = new staff('小周', '女', 'f')
let staff4 = new staff('小鄭', '男', 'g')

leaderStaff1.add(leaderStaff3);
leaderStaff1.add(leaderStaff2);
leaderStaff1.add(leaderStaff4)
leaderStaff1.add(staff1)

leaderStaff3.add(staff4)
leaderStaff3.add(staff3)
leaderStaff4.add(staff2)
// staff3.add(staff5)

leaderStaff1.doCount()

執行結果如圖:

執行結果-4.png

這樣看起來似乎樹形結構不是很明顯,所以我們再看下中間執行的數據:

數據-5.png

這裏我們把擁有下級(子元素)的都統稱爲老闆(leaderStaff),沒有下級(子元素)的都叫做員工(staff),員工下面在沒有子元素,否則拋出錯誤。他們都有doCount這個方法。很明顯leaderStaff1(大旺)有四個子元素,其中大李又有兩個子元素,以及大無有一個子元素,這些都是被稱作組合對象,使用的人也不需要分辨他是孩子對象還是祖先元素。

這裏我們改變樹的結構,增加新的數據,卻不用修改原來的代碼,這是可取的,符合開放-封閉原則

四、注意事項

我們現在瞭解了組合模式,還要說下在使用組合模式的使用,有一些值得我們注意的地方:

  1. 組合模式不是父子關係
    組合模式的樹形結構容易讓人誤以爲組合對象和葉子對象之間是父子關係,但並不是這樣的。組合模式是一種HAS-A(聚合)的關係,而不是IS-A,組合對象把請求委託給它所包􏶺的所有􏷉葉對象,它們能􏲑能夠合作的關鍵是擁有相同是的接口

  2. 對葉對象操作的一致性
    組合模式除了要求組合對象和也對象擁有相同的接口之外,還有一個必要條件,就是對一組也對象的操作必須具有一致性

  3. 雙向映射關係
    上面的例子中,公司給員工發送通知的步驟是從公司到各個部分,再到小組,最後纔到每個員工的郵箱裏,這便是一個組合模式的例子,但是存在特殊情況,一個員工同屬於多個部門的時候就沒有了嚴格意義上的層次結構,在這種情況下便不再適用組合模式,否則該員工很可能收到兩份郵件

  4. 用職責鏈模式提高組合模式性能
    一些情況下,生成樹的結構十分複雜,節點數量衆多,在遍歷的過程中,消耗性能。職責鏈模式就是在遍歷的過程中,只讓請求順着聯調從父元素往子元素上傳遞,或子元素往父元素身上船體,知道遇到能處理請求的對象未知,避免浪費

五、總結

雖然多數時候組,合模式帶給我們便利,讓我們可以使用樹形的方式去創建對象結構,卡可以忽略組合對象和單個對象之間的差別,使用一致的方式處理。但是,它的缺點也需要注意:他們的區別只有在運行的時候纔會體現出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章