合成模式屬于對象的結構模式,有時又叫做“部分-整體”模式。合成模式将對象組織到樹結構中,可以用來描述整體與部分的關系。合成模式可以使用戶端将單純元素與複合元素同等看待。 (學習)
合成模式
合成模式把部分和整體的關系用樹結構表示出來。合成模式使得用戶端把一個個單獨的成分對象和由它們複合面成的合成對象同等看待。
比如,一個檔案系統就是一個典型的合成模式系統。下圖是常見的計算機xp檔案系統的一部分。
從上圖可以看出,檔案系統是一個樹結構,樹上長有節點。樹的節點有兩種,一種是樹枝節點,即目錄,有内部樹結構,在圖中塗有顔色;另一種是檔案,即樹葉節點,沒有内部樹結構。
顯然,可以把目錄和檔案當做同一種對象同等對待和處理,這也是就是合成模式的應用。
合成模式可以不提供父對象的管理方法,但是合成模式必順在合适的地方提供子對象的管理方法,如:add()、remove()、以及getChild()等。
合成模式的實作根據所實作接口的差別分為兩種形式,分别稱為安全式和透明式。
安全式合成模式的結構
安全模式的合成模式要求管理聚集的方法隻出現在樹枝構件類中,而不出現在樹葉構件類中。
這種形式涉及到三個角色
- 抽象構件(Component)角色:
這是一個抽象角色,它給參加組合的對象定義出公共的接口及其預設行為,可以用來管理所有的子對象。合成對象通常把它所包含的子對象當做類型為Component的對象。在安全式的合成模式裡,構件角色并不定義出管理子對象的方法,這一定義由樹枝構件對象給出。
- 樹葉構件(Leaf)角色:
樹葉對象是沒有下級子對象的對象,定義出參加組合的原始對象的行為。
- 樹枝構件(Composite)角色:
代表參加組合的下級子對象的對象。樹枝構件類給出所有的管理子對象的方法,如add()、removed()以及getChild()。
源代碼
package Composite
/**
* 抽象構件角色
* */
interface Component {
/**
* 輸出元件自身的名稱
* */
fun printStruct(preStr: String)
}
package Composite
/**
* 樹枝構件角色
* */
class Composite constructor(name: String) : Component {
/**
* 用于存儲組合對象中包含的子元件對象
* */
private val childComponents = ArrayList<Component>()
private var name = ""
init {
this.name = name
}
fun addChild(child: Component) {
childComponents.add(child)
}
fun removeChild(index: Int) {
childComponents.removeAt(index)
}
fun getChildren(): List<Component> {
return childComponents
}
/**
* 輸出對象的自身結構
* @param preStr 字首,主要是按照層級控制空格,實作向後縮進
* */
override fun printStruct(preStr_: String) {
var preStr = preStr_
//選輸出自已
println("$preStr_ + $name")
if (childComponents != null) {
preStr += " "
for (child in childComponents) {
//遞歸輸出每個子對象
child.printStruct(preStr)
}
}
}
}
package Composite
/**
* 樹葉建構角色類
* */
class Leaf constructor(name:String) : Component {
private var name = ""
init {
this.name = name
}
/**
* 輸出葉子對象的結構,葉子沒有子對象,也是就輸出葉子對象的名字
* @param preStr 字首,主要是按照層級拼接的空格,實作向後縮進
* */
override fun printStruct(preStr: String) {
println("$preStr - $name")
}
}
測試運作:
val root = Composite("Clothing")
val c1 = Composite("Mens")
val c2 = Composite("Womens")
val leaf1 = Leaf("Shirt")
val leaf2 = Leaf("Jacket")
val leaf3 = Leaf("Skirt")
val leaf4 = Leaf("Suit")
root.addChild(c1)
root.addChild(c2)
c1.addChild(leaf1)
c1.addChild(leaf2)
c2.addChild(leaf3)
c2.addChild(leaf4)
root.printStruct("")
結果:
+ Clothing
+ Mens
- Shirt
- Jacket
+ Womens
- Skirt
- Suit
可以看出,樹枝構件類(Composite)給出了addChild()、removeChild()以及getChild()等方法的聲明和實作,而樹葉構件類則沒有給出這些方法的聲明或實作。這樣的做法是安全的做法,由于這個特點,用戶端應用程式不可能錯誤地調用樹葉構件的聚集方法,因為樹葉構件沒有這些方法,調用會導緻編譯錯誤。
安全式合成模式的缺點是不夠透明,因為樹葉類和樹枝類将具有不同的接口。
透明式合成模式的結構
與安全式的合成模式不同的是,透明式的合成模式要求所有的具體構件類,不論樹枝構件還是樹葉構件,均符合一個固定接口。
package Composite.Transparent
/**
* 抽象建構角色類
* */
abstract class Component {
/**
* 輸出元件自身的名稱
* */
abstract fun printStruct(preStr: String)
/**
* 增加一個子構件對象
* @param child 子構件對象
* */
open fun addChild(child: Component) {
//預設實作,抛出異常,因為葉子對象沒有此功能
//或者子元件沒有實作這個功能
throw UnsupportedOperationException("對象不支援此功能")
}
open fun removeChild(index: Int) {
//預設實作,抛出異常,因為葉子對象沒有此功能
//或者子元件沒有實作這個功能
throw UnsupportedOperationException("對象不支援此功能")
}
open fun getChild(): List<Component> {
//預設實作,抛出異常,因為葉子對象沒有此功能
//或者子元件沒有實作這個功能
throw UnsupportedOperationException("對象不支援此功能")
}
}
package Composite.Transparent
/**
* 樹枝建構角色類
* */
class Composite constructor(name: String) : Component() {
private val childComponents = ArrayList<Component>()
private var name = ""
init {
this.name = name
}
override fun addChild(child: Component) {
childComponents.add(child)
}
override fun removeChild(index: Int) {
childComponents.removeAt(index)
}
override fun getChild(): List<Component> {
return childComponents
}
/**
* 輸出對象自身建構
* @param preStr 字首,主要是按照層級拼接空格,實作向後縮進
* */
override fun printStruct(preStr_: String) {
var preStr = preStr_
println("$preStr + $name")
if (childComponents != null) {
//添加兩個空格,表示向後縮進兩個空格
preStr += " "
//輸出目前對象的子對象
for (child in childComponents) {
//遞歸輸出每個子對象
child.printStruct(preStr)
}
}
}
}
package Composite.Transparent
class Leaf constructor(name: String) : Component() {
private var name = ""
init {
this.name = name
}
/**
* 輸出葉子對象的結構,葉子對象沒有子對象,也就是輸出葉子對象的名字
* @param preStr 字首,主要是按照層級拼接的空格,實作向後縮進
*/
override fun printStruct(preStr: String) {
println("$preStr - $name")
}
}
運作測試,客戶類的主要變化是不再區分Composite對象和Leaf對象。
val root = Composite("Clothing")
val c1 = Composite("Mens")
val c2 = Composite("Womens")
val leaf1 = Leaf("Shirt")
val leaf2 = Leaf("Jacket")
val leaf3 = Leaf("Skirt")
val leaf4 = Leaf("Suit")
root.addChild(c1)
root.addChild(c2)
c1.addChild(leaf1)
c1.addChild(leaf2)
c2.addChild(leaf3)
c2.addChild(leaf4)
root.printStruct("")
運作結果
+ Clothing
+ Mens
- Shirt
- Jacket
+ Womens
- Skirt
- Suit
可以看出,如果在java中用戶端無需再區分操作的是樹枝對象(Composite)還是樹葉對象(Leaf)了;對于用戶端而言,操作的都是Component對象。
兩種實作方法的選擇
這裡所說的安全性合成模式工是指:從用戶端使用合成模式上看是否更安全,如果是安全的,那麼就不會有發生誤操作的可能,能通路的方法都是被支援的。
這裡所說的透明性合成模式是指:從用戶端使用合成模式上,是否需要區分到底是“樹枝對象”還是“樹葉對象”。如果是透明的,那就不用區分,對于客戶而言,都是Component對象,具體的類型對于客 戶端而言是透明的,是無順關心的。
對于合成模式而言,在安全性和透明性上,會更看重透明性,畢竟合成模式的目的是:讓用戶端不再區分操作的是樹枝對象還是樹葉對象,而是以一個統一的方式來操作。
而且對于安生性的實作,需要區分是樹枝對象還是樹葉對象。有時候,需要将對象進行類型轉換,卻發現類型資訊丢失了,隻好強行轉換,這種類型轉換必然是不夠安全的。
是以在使用合成模式的時候,建議多采用透明性的實作方式。