1. 程式人生 > >JAVA設計模式--組合模式

JAVA設計模式--組合模式

一、什麼是組合模式

組合(Composite)模式是一種物件的行為模式。將物件組合成樹形結構以表示“部分-整體”的層次結構。組合模式使得使用者對單個物件和組合物件的使用具有一致性。
組合模式的本質:統一葉子物件和組合物件。
組合模式的目的:讓客戶端不再區分操作的是組合物件還是葉子物件,而是以一個統一的方式來操作。

二、組合模式的適用性

在開發中, 我們經常可能要遞迴構建樹狀的組合結構,比如以下的商品類別樹:


仔細觀察上面的商品類別樹,有以下幾個明顯的特點。
• 有一個根節點,比如“服裝”,它沒有父節點,它可以包含其他的節點。
• 樹枝節點,有一類節點可以包含其他的節點,稱之為樹枝節點,比如“男裝”、“女裝”和“母嬰”。
• 葉子節點,有一類節點沒有子節點,稱之為葉子節點,比如“襯衣”、“夾克”、“裙子”、“套裝”等。
如果碰到類似上面這種,需使用物件樹來描述或實現的功能,都可以考慮使用組合模式,比如讀取XML檔案,或是對語句進行語法解析等。

三、組合模式的結構


組合模式涉及的角色及其職責如下:

抽象元件(Component)角色:為組合物件和葉子物件宣告公共的介面,讓客戶端可以通過這個介面來訪問和管理整個物件樹,並可以為這些定義的介面提供預設的實現。
組合物件(Composite)角色:通常會儲存子元件(組合物件、葉子物件),定義包含子元件的那些元件的行為,並實現在抽象元件中定義的與子元件有關的操作,例如子元件的新增(addChild)和刪除(removeChild)等。
葉子物件(Leaf)角色:定義和實現葉子物件的行為,並且它不再包含其他的子節點物件。
客戶端(Client)角色:通過Component介面來統一操作組合物件和葉子物件,以創建出整個物件樹結構。
組合模式結構示意原始碼如下:

先看看抽象元件類的定義,示例程式碼如下。

/**
 * 抽象的元件物件,為組合中的物件宣告介面,實現介面的預設行為
 */
public abstract class Component {

	// 示意方法,子元件物件可能有的功能方法
	public abstract void someOperation(String preStr);

	public void addChild(Component child) {
		// 預設的實現,丟擲異常,因為葉子物件沒有這個功能,或子類未實現這個功能
		throw new UnsupportedOperationException("物件不支援此功能");
	}

	public void removeChild(Component child) {
		// 預設的實現,丟擲異常,因為葉子物件沒有這個功能,或子類未實現這個功能
		throw new UnsupportedOperationException("物件不支援此功能");
	}

	public Component getChildren(int index) {
		// 預設的實現,丟擲異常,因為葉子物件沒有這個功能,或子類未實現這個功能
		throw new UnsupportedOperationException("物件不支援此功能");
	}
}

接下來看看組合類的定義,示意程式碼如下。

import java.util.ArrayList;
import java.util.List;

public class Composite extends Component {

	/**
	 * 示意屬性,元件的名字
	 */
	private String name = "";

	public Composite(String name) {
		this.name = name;
	}

	/**
	 * 用來儲存組合物件中包含的子元件物件
	 */
	private List<Component> childComponents = null;

	/**
	 * 示意方法,此處用於輸出元件的樹形結構,通常在裡面需要實現遞迴的呼叫
	 */
	@Override
	public void someOperation(String preStr) {
		// 先把自己輸出
		System.out.println(preStr + "+" + name);
		// 如果還包含其他子元件,那麼就輸出這些子元件物件
		if (null != childComponents) {
			// 新增一個空格,表示向後縮排一個空格
			preStr += "   ";
			// 輸出當前物件的子元件物件
			for (Component component : childComponents) {
				// 遞迴地進行子元件相應方法的呼叫,輸出每個子元件物件
				component.someOperation(preStr);
			}
		}

	}

	/**
	 * 向組合物件中新增元件物件
	 */
	public void addChild(Component child) {
		// 延遲初始化
		if (null == childComponents) {
			childComponents = new ArrayList<Component>();
		}
		childComponents.add(child);
	}

	/**
	 * 從組合物件中移除元件物件
	 */
	public void removeChild(Component child) {
		if (null != childComponents) {
			childComponents.remove(child);
		}
	}

	/**
	 * 根據索引獲取組合物件中對應的元件物件
	 */
	public Component getChildren(int index) {
		if (null != childComponents) {
			if (index >= 0 && index < childComponents.size()) {
				return childComponents.get(index);
			}
		}
		return null;
	}
}

再來看看葉子類的定義,示例程式碼如下。

public class Leaf extends Component {

	/**
	 * 示意屬性,元件的名字
	 */
	private String name = "";

	public Leaf(String name) {
		this.name = name;
	}

	/**
	 * 示意方法,此處用於輸出元件的樹形結構
	 */
	@Override
	public void someOperation(String preStr) {
		System.out.println(preStr + "-" + name);
	}

}

在客戶端中使用Component介面來操作組合物件結構,示意程式碼如下。

public class Client {

	public static void main(String[] args) {
		// 定義多個Composite組合物件
		Component root = new Composite("服裝");
		Component c1 = new Composite("男裝");
		Component c2 = new Composite("女裝");
		Component c3 = new Composite("母嬰");

		// 定義多個Leaf葉子物件
		Component leaf1 = new Leaf("西服");
		Component leaf2 = new Leaf("夾克");
		Component leaf3 = new Leaf("襯衫");
		Component leaf4 = new Leaf("裙子");
		Component leaf5 = new Leaf("套裝");
		Component leaf6 = new Leaf("鞋襪");
		Component leaf7 = new Leaf("孕婦裝");
		Component leaf8 = new Leaf("嬰兒裝");

		// 組合成為樹形的物件結構
		root.addChild(c1);
		root.addChild(c2);
		root.addChild(leaf6);
		c1.addChild(leaf1);
		c1.addChild(leaf2);
		c1.addChild(leaf3);
		c2.addChild(leaf4);
		c2.addChild(leaf5);
		c2.addChild(c3);
		c3.addChild(leaf7);
		c3.addChild(leaf8);

		// 呼叫根物件的輸出功能輸出整棵樹
		root.someOperation("");
	}

}
執行程式列印結果如下:
+服裝
   +男裝
      -西服
      -夾克
      -襯衫
   +女裝
      -裙子
      -套裝
      +母嬰
         -孕婦裝
         -嬰兒裝
   -鞋襪
從以上的示例可以看出,組合模式的關鍵就在於抽象元件角色,作為組合物件和葉子物件的父類,這個抽象元件類既可以代表葉子物件,也可以代表組合物件,這樣使用者在操作的時候,始終是在操作元件物件,不必再去區分是在操作組合物件還是葉子物件,從而使得對葉子物件和組合物件的使用具有了一致性。

四、組合模式的安全性和透明性

• 組合模式的安全性是指:從客戶使用組合模式上看是否更安全。如果是安全的,那麼就不會有發生誤操作的可能,能訪問的方法都是被支援的功能。
• 組合模式的透明性是指:從客戶使用組合模式上看是否需要區分到底是組合物件還是葉子物件。如果是透明的,那就不用再區分,對於客戶而言,都是元件物件,具體的型別對於客戶而言是透明的,是客戶無須關心的。

透明性的實現:

如果把管理子元件的操作定義在Component中,那麼客戶端只需要面對Component,而無須關心具體的元件型別,這種實現方式就是透明性的實現。前面結構示意程式碼中就是採用的這一實現方式。
但是透明性的實現是以安全性為代價的,因為在Component中定義的一些方法,對於葉子物件來說是沒有意義的,比如增加、刪除子元件物件。但這些方法對客戶卻是透明的,因此客戶可能會對葉子物件呼叫這種增加或刪除子元件的方法,這樣的操作是不安全的。
組合模式的透明性實現,通常的方式是:在Component中宣告管理子元件的操作,並在Component中為這些方法提供預設的實現,對於葉子物件不支援的功能,可以直接丟擲一個異常,來表示不支援這個功能。

安全性的實現:


如果把管理子元件的操作定義在Composite中,那麼客戶端在使用葉子物件的時候,就不會發生使用新增子元件或是刪除子元件的操作了,因為壓根就沒有這樣的功能,這種實現方式是安全的。

但是這樣一來,客戶端在使用的時候,就必須區分到底使用的是Composite物件,還是葉子物件,不同物件的功能是不一樣的。

兩種實現方式的選擇:

對於組合模式而言,在安全性和透明性上,會更看重透明性,畢竟組合模式的功能就是要讓使用者對葉子物件和組合物件的使用具有一致性。
因此,在使用組合模式的時候,應多采用透明性的實現方式,少用安全性的實現方式。

五、組合模式的優缺點

使用組合模式的優點:

   1)  統一了組合物件和葉子物件。
   2)  簡化了客戶端呼叫,無須區分操作的是組合物件還是葉子物件。
   3)  更容易擴充套件,有了Component的約束,新定義的Composite或Leaf子類能夠很容易地與已有的結構一起工作。

使用組合模式的缺點:

   1)  很難限制組合中的元件型別。

六、總結

組合模式通過把葉子物件當成特殊的組合物件看待,從而對葉子物件和組合物件一視同仁,全部當成了Component物件,有機地統一了葉子物件和組合物件。
正是因為統一了葉子物件和組合物件,在將物件構建成樹形結構的時候,才不需要做區分,反正是元件物件裡面包含其他的元件物件,如此遞迴下去:也才使得對於樹形結構的操作變得簡單,不管物件型別,統一操作。