更多請移步: 我的博客
目的
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,你通過一個通用接口來操作它。並且,這個結構可以自己傳遞請求。
現實世界的類比
軍事結構
大多數國家的軍隊看起來就像組合樹。最底層,是一些士兵。它們組合成班。班組合成排。排組合成師。最後,幾個師組成軍隊。
命令下達給層級的最高層,然後層層向下傳遞直到每個士兵都知道他們要做什麼。
結構
Component爲這個樹結構的所有簡單的和複雜的元素聲明一個通用的接口。
Leaf是一個樹的一個基本元素,它沒有子節點。
因此它們沒有任何委託對象,葉子節點通暢要做大多真正的工作。Container(也叫做Composite)是一個有子節點的元素:葉子(Leaves)或其他容器(Container 或 Composite組合)。容器不需要知道子節點的類型,因爲它們都實現了Component接口。
收到請求後,容器把它們委託給子節點,然後在返回給客戶端之前運行併合計結果。
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模式提供兩種基本元素:簡單葉子和可以存儲其他葉子或者其他容器等的複雜容器。模式強制容器和它的子元素遵循通用的接口,這樣就允許遞歸整個樹結構操作。當客戶端應該統一處理簡單和複雜的元素時。
感謝通用葉子和容器均遵循了通用接口,客戶端代碼不需要關心協作對象的類型。
如何實現
確保你的業務邏輯可能夠被當作樹結構。嘗試把它們分離成簡單元素和容器。切記,容器能夠包含基本元素和其他容器。
定義Components(組件)的通用接口。它應該包含對簡單和複雜組件來講都合理的操作。
創建代表基本組件的Leaf(葉子)類。順便說下,一個程序中可以有多個葉子類。
創建擁有可以存儲子組件(數組)的Container類。這個字段可以存儲葉子和容器,所以它要被聲明爲Component類型。
在實現Component接口的方法時,記住,Container應該把它的多數工作委託給子組件。最後,爲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 組件)
鑑別:組合模式很容易識別,它們的行爲方法都有一個在樹結構中具有相同抽象/接口類型的實例。