設計模式-結構模式之Composite

更多請移步: 我的博客

目的

Composite是結構設計模式的一種,允許你像樹一樣組合對象,並且允許客戶端像單個對象一樣和這些結構協作。

問題

Composite模式只有在你的業務模型可以被表示爲一個樹結構時纔有意義。

比如,你有兩個對象:Product和Box。這個Box可以包含幾個Product和一些更小的Box。這些更小的Box也可以包含一些Product或者更小的Box等等。

現在,想象你的Product和Box是訂單的一部分。計算訂單的總價會非常困難。你拿到了一個大的Box,打開後看到它裏面還有:ProductA,ProductB或者其他的Box,讓我們再看看它裏面有什麼…不久,你將會停在一堆膠帶和紙盒上,但仍在嘗試計算總價。

那麼,有更好的方法嗎?

解決

Composite模式建議通過讓Product和Box都遵循一個擁有getPrice()方法的通用接口來解決這個問題。

對Product來講,它僅需要返貨這個產品的價格。但是Box需要有更多的有趣行爲。一個Box需要遍歷它的內容並且尋味每一個商品(item)的價格。所以,如果一個商品是產品,它會立即返回價格。如果是一個更小的Box,它也需要遍歷它自己商品的價格直到返回一個總價。一旦小計計算出來,Box甚至可以對總價做一些額外操作,像包裝價之類。

現在,通過這個途徑我們不必太過關心樹的具體對象。不管是單個Product還是複雜的Box,你通過一個通用接口來操作它。並且,這個結構可以自己傳遞請求。

現實世界的類比

軍事結構

大多數國家的軍隊看起來就像組合樹。最底層,是一些士兵。它們組合成班。班組合成排。排組合成師。最後,幾個師組成軍隊。

命令下達給層級的最高層,然後層層向下傳遞直到每個士兵都知道他們要做什麼。

結構

structure

  1. Component爲這個樹結構的所有簡單的和複雜的元素聲明一個通用的接口。

  2. Leaf是一個樹的一個基本元素,它沒有子節點。
    因此它們沒有任何委託對象,葉子節點通暢要做大多真正的工作。

  3. Container(也叫做Composite)是一個有子節點的元素:葉子(Leaves)或其他容器(Container 或 Composite組合)。容器不需要知道子節點的類型,因爲它們都實現了Component接口。

    收到請求後,容器把它們委託給子節點,然後在返回給客戶端之前運行併合計結果。

  4. Client通過Component接口來使用樹上的所有元素。
    因此,客戶端不需要關心它到底是在用簡單的葉子還時複雜的容器。

僞代碼

在這個例子中,Composite模式幫助我們實現堆疊幾何形狀。

CompoundGraphic是一個容器,由任意數量的子形狀,包括其他組合形狀。組合形狀和簡單形狀擁有相同的方法。但是,組合形狀自己並不做實際的工作,它把請求傳遞給子形狀,遞歸遍歷自己的葉子節點和組合形狀下的子節點。最後容器合併結果返回給客戶端。

客戶端通過接口和所有形狀協作,這些形狀都遵循這個接口。這樣,客戶端代碼可以與非常複雜的結構一起工作,而不需要耦合具體的樹形元素類。

// Common interface for all components.
interface Graphic is
    method move(x, y)
    method draw()

// Simple component.
class Dot implements Graphic is
    field x, y

    constructor Circle(x, y) { ... }

    method move(x, y) is
        this.x += x, this.y += y

    method draw() is
        Draw a dot at X and Y.

// Components could extend other components.
class Circle extends Dot is
    field radius

    constructor Circle(x, y, radius) { ... }

    method move(x, y) is
        this.x = x, this.y = y

    method draw() is
        Draw a circle at X and Y and radius R.

// The composite component includes methods to add/remove child components. It
// tries to delegates to its children all operations defined in the
// component interface.
class CompoundGraphic implements Graphic is
    field children: array of Graphic

    method add(child: Graphic) is
        Add child to children array.

    method remove(child: Graphic) is
        Remove child to children array.

    method move(x, y) is
        For each child: child.move(x, y)

    method draw() is
        Go over all children and calculate bounding rectangle.
        Draw a dotted box using calculated values.
        Draw each child.


// Application can operate with specific components or whole groups.
class ImageEditor is
    method load() is
        all = new CompoundGraphic()
        all.add(new Dot(1, 2))
        all.add(new Circle(5, 3, 10))
        // ...

    method groupSelected(components: array of Graphic) is
        group = new CompoundGraphic()
        group.add(components)
        all.remove(components)
        all.add(group)
        // All components will be drawn.
        all.draw()

適用性

  • 當你需要實現一個像樹一樣有着簡單元素和容器的結構
    Composite模式提供兩種基本元素:簡單葉子和可以存儲其他葉子或者其他容器等的複雜容器。模式強制容器和它的子元素遵循通用的接口,這樣就允許遞歸整個樹結構操作。

  • 當客戶端應該統一處理簡單和複雜的元素時。
    感謝通用葉子和容器均遵循了通用接口,客戶端代碼不需要關心協作對象的類型。

如何實現

  1. 確保你的業務邏輯可能夠被當作樹結構。嘗試把它們分離成簡單元素和容器。切記,容器能夠包含基本元素和其他容器。

  2. 定義Components(組件)的通用接口。它應該包含對簡單和複雜組件來講都合理的操作。

  3. 創建代表基本組件的Leaf(葉子)類。順便說下,一個程序中可以有多個葉子類。

  4. 創建擁有可以存儲子組件(數組)的Container類。這個字段可以存儲葉子和容器,所以它要被聲明爲Component類型。
    在實現Component接口的方法時,記住,Container應該把它的多數工作委託給子組件。

  5. 最後,爲Container實現add/remove子元素的方法。
    記住,這些操作應該被放在Component接口中。它餵飯了接口分離原則,因爲這些方法在Leaf類中是空的。但是另一方面,樹中所有的組件變得與客戶端的立場相當。

優點

  • 簡化必須與複雜樹結構交互的客戶端代碼。

  • 添加新組件類型變的簡單。

缺點

  • 創建了一個太過通用的類設計。

和其他模式的關係

  • Builder可以用來一步一步的構建一個複雜的Composite。

  • Chain of Resopnsibility通常和Composite結合使用。在這種情況下,組件的父級可以作爲其後繼。

  • Iterator可以用來遍歷Composite樹。

  • Visitor用來操作Composite樹的實體。

  • Flyweight場合Composite結合使用來實現葉子結點共享和保存RAM。

  • Composite和Decorator結構圖很相似,因爲它們都依賴遞歸組合來組織開放數量的對象。
    Decorator可以看作只有一個組件的弱化Composite。然而,Decorator給對象賦予了額外責任,而Composite僅是對所有擁有相同行爲的子元素做了“合併”。

    但是它們也可以合作:Composite能夠用Decorator改變樹組件的行爲。

  • 使用Composite和Decorator模式使得設計笨重,可以使用Prototype來簡化。它允許克隆複雜結構,而不是重新構造它們。

Java中模式的使用

用例:Composite模式在Java代碼中很常見。它常用來表示用戶接口組件的層級或者和圖像一起使用的代碼。

這有一些在標準Java苦衷使用組合的例子:

  • java.awt.Container#add(Component) (幾乎遍及Swing組件)

  • javax.faces.component.UIComponent#getChildren() (幾乎遍及JSF UI 組件)

鑑別:組合模式很容易識別,它們的行爲方法都有一個在樹結構中具有相同抽象/接口類型的實例。

參考

翻譯整理自:https://refactoring.guru/design-patterns/composite

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