Composite 組合模式 | 實現與解說 | 物件導向設計

Composite 組合模式 | 實現與解說 | 物件導向設計

物件導向設計模式是軟體開發中的重要概念之一,而Composite(組合)模式是其中一種常用的設計模式之一。本文將探討Composite模式的實現與解說,以及其使用場景、UML圖示、設計優缺點,最後將深入研究Composite模式的實際實現,包括安全模式和透明模式的討論

透過本文的學習,您將更加深入了解Composite模式的運作原理和應用場景,有助於您在軟體開發中更加靈活地應用設計模式

可以把一組相似的物件,看做一個物件,就像是 數據結構 Tree,可以簡單把樹分成樹枝、葉,不斷的重複以組成一個樹(如果對這句話不清楚的話,可以先看以下的解說)


Composite 使用場景

  • 使用者對於單個物件 & 組合物件的使用具有 一致性
  • 從一個整體中能夠 獨立出部分模組 的功能或場景,使用上又有關聯性質 基本上有樹狀結構,這個設計模式都可以使用

Composite UML

  • Composite 設計模式的 UML 角色介紹
功能
Component(成分)末端節點與樹枝節點的共通點
Composite(合成)組裝模式的核心類1. 繼承 Component 並且 2.內部聚集 Component 類
Left其下不會再有子節點

Composite 設計優缺點

  • 優點 :
    1. 高層模組可以一致的==使用同一個組合結構或單一物件,實做部分讓低層模組做
    2. 清楚定義分層重複的物件,它讓高層模組忽略了層次的差異,方便對整個層次結構進行況控制
  • 缺點 :
    在新增物件時 不好對枝幹中的類型進行限制 (如果要限制加入的物件類型),大多數情況下它們都來自相同的抽象層,此時必須進行類行檢查,過程較複雜

Composite 實現

Composite 實現有分為兩種方式,1. 安全模式、2. 透明模式,兩者各有優缺長短

Composite 標準:安全模式

A. Component:樹枝、根的共通點

abstract class Component constructor(val name: String) {

    abstract fun printInfo()

}

B. Left:最基礎沒有子類的葉節點

class Department constructor(name : String) : Component(name) {

    override fun printInfo() {
        println("The department name is $name")
    }

}

C. Composite:組合模式的核心,繼承 Component,並聚合 Component 抽象作為其頁節點

class Company constructor(name: String) : Component(name) {

    private val childList = mutableListOf<Component>()

    override fun printInfo() {
        childList.iterator().apply {
            while (hasNext()) {
                next().printInfo()
            }
        }
    }

    fun addDepartment(child: Component) {
        childList.contains(child).let {  contains ->
            if (contains) {
                return
            }

            childList.add(child)
        }
    }

    fun removeDepartment(child: Component) {
        childList.remove(child)
    }

    fun getDepartmentList() : Iterator<Component> = childList.iterator()


}

● 使用 Composite 設計的範例

fun main() {
    val company = Company("AUSU 總公司")

    // 新建一個根結點 Company (Root),多個子結點
    // 可以簡單看出其實就分為兩個部分,終端節點 & 分枝節點
    Department("總-公關部門").also {
        
        // 不符合依賴倒置
        company.addDepartment(it)
    }
    Department("總-業務部門").also {
        company.addDepartment(it)
    }
    Department("總-研發部門").also {
        company.addDepartment(it)
    }
    Department("總-財務部門").also {
        company.addDepartment(it)
    }

    Company("AUSU 子公司").also {
        company.addDepartment(it)
    }.let { child ->
        Department("子-公關部門").also {
            child.addDepartment(it)
        }
        Department("子-業務部門").also {
            child.addDepartment(it)
        }
    }

    company.printInfo()
}

在使用的過程中,我們可以很清楚地發現,在添加子類時,必須使用實體實作類別的 addDepartment 方法,這就 不符合依賴倒置 的原則

Composite 變化:透明模式

●  從上面的安全模式下,我們可以發現「使用者必須取得 Composite 實作類」才能操作,但這就不符合依賴倒置規則;為了要符合依賴倒置的原則,我們可以把實現抽象化到 Component

●  透明的關鍵點在於,使用者在使用時不會再區分「根、葉」節點

可以從抽象類透明的看到所有的節點有的功能(但是實做上由節點決定如何處理)

如果使用這種方式,建議使用「契約式」設計,將先驗條件定義在註解中(像是可能會拋出甚麽異常之類的)

●  抽象化到 Component 層 副作用

A. 其實就是要注意到,抽象化過多會導致其他類必須處理(實作、繼承)不必要的功能,會造成類的膨脹

這要根據業務需求去衡量

B. 處理不當會造成 Runtime 時出錯

A. Component:修改該類,讓它先告所有方法,對外暴露(透明化)所有方法

abstract class TransparentComponent 
        val name: String) {
            
    protected val childList = mutableListOf<TransparentComponent>()

    abstract fun printInfo()

    abstract fun addDepartment(child: TransparentComponent)

    abstract fun removeDepartment(child: TransparentComponent)

    abstract fun getDepartmentList() : Iterator<TransparentComponent>

}

B. Left:對於不需要的方法拋出 UnsupportedOperationException 異常

class Department2 constructor(name: String) : TransparentComponent(name) {
    override fun printInfo() {
        println("The department name is $name")
    }

    override fun addDepartment(child: TransparentComponent) {
        throw UnsupportedOperationException()
    }

    override fun removeDepartment(child: TransparentComponent) {
        throw UnsupportedOperationException()
    }

    override fun getDepartmentList(): Iterator<TransparentComponent> {
        throw UnsupportedOperationException()
    }

}

C. Composite:該類的實現基本沒變

class Company2 constructor(name: String) : TransparentComponent(name) {
    override fun printInfo() {
        childList.iterator().apply {
            while (hasNext()) {
                next().printInfo()
            }
        }
    }

    override fun addDepartment(child: TransparentComponent) {
        childList.contains(child).let {  contains ->
            if (contains) {
                return
            }

            childList.add(child)
        }
    }

    override fun removeDepartment(child: TransparentComponent) {
        childList.remove(child)
    }

    override fun getDepartmentList(): Iterator<TransparentComponent> = childList.iterator()

}

● 使用 Composite 設計的範例

fun main() {
    // 依賴抽象
    val company : TransparentComponent = Company2("AUSU 總公司")

    Department2("總-公關部門").also {
        company.addDepartment(it)
    }
    Department2("總-業務部門").also {
        company.addDepartment(it)
    }
    Department2("總-研發部門").also {
        company.addDepartment(it)
    }
    Department2("總-財務部門").also {
        company.addDepartment(it)
    }

    Company2("AUSU 子公司").also {
        company.addDepartment(it)
    }.let { child ->
        Department2("子-公關部門").also {
            child.addDepartment(it)
        }
        Department2("子-業務部門").also {
            child.addDepartment(it)
        }
    }

    company.printInfo()
}

更多的物件導向設計

物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!

設計建模 2 大概念- UML 分類、使用

物件導向設計原則 – 6 大原則(一)

物件導向設計原則 – 6 大原則(二)

創建、行為、結構型設計 8 個比較 | 包裝模式 | 最佳實踐

創建模式 Creation Patterns

創建模式 PK

創建模式 - Creation Patterns

結構模式 Structural Patterns

結構模式 PK

結構模式 - Structural Patterns

結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結

Bridge 橋接模式 | 解說實現 | 物件導向設計

Decorate 裝飾模式 | 解說實現 | 物件導向設計

Proxy 代理模式 | 解說實現 | 分析動態代理

Iterator 迭代設計 | 解說實現 | 物件導向設計

Facade 外觀、門面模式 | 解說實現 | 物件導向設計

Adapter 設計模式 | 解說實現 | 物件導向設計

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響