Java設計模式之組合模式(Composite)

一、概述

我們常常可以看到這樣一種形式,比如說電腦中的磁盤管理,我的電腦中有C盤、D盤、E盤,在C盤中又有A文件夾、B文件夾等,在A文件夾下又有A1文件夾、A2文件夾、A3文件等等;再比如說在桌子上有揹包、電腦、水杯、書架、筆筒等,在揹包裏有書、本子、IPad等,在書架上有書A、書B等,在筆筒裏有中性筆、鋼筆、鉛筆、彩筆等;再比如北京公司總部有財務部、人力部、上海分公司、深圳分公司等,在上海分公司又有財務部、人力部、南京辦事處、杭州辦事處等,在南京辦事處下面又有財務部、人力部等。我這麼說,可能看不出什麼,下面用圖表示一下這3個例子。

圖1 我的電腦結構示意圖

圖2 桌面結構示意圖

圖3 公司結構示意圖 

這樣一看他們結構是不是一目瞭然,其實就是樹形結構,有根、有枝幹、有葉子。如果這個時候boss拋來一個需求,讓所有的枝幹具有一定的功能A,所有的葉子具有一定的功能B,或者,更具體點,從圖3來說,要求所有公司的財務部具有相同的功能A,人力部具有功能B,這個時候我們要怎麼實現呢。

噹噹噹當~ 組合模式應運而生!

二、組合模式

1. 定義

組合模式,將對象組合成樹形結構以表示“部分-整體”的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。(引自《大話設計模式》)

2. 基本結構圖

Component:抽象類或接口,爲組合中的對象聲明接口。通過add和remove方法提供增加和刪除樹枝、樹葉的功能。

Leaf:葉子,爲組合中最末端的節點,沒有子節點。

Composite:枝節點,上有父節點,下有子節點。定義了枝節點的行爲,用來存儲子部件。在Component接口中實現與子部件有關的操作——增加add和刪除remove。

3. 基本代碼

Component抽象類

public abstract class Component {
	protected String name;
	
	public Component(String name) {
		this.name = name;
	}
	
	public abstract void add(Component c);
	public abstract void remove(Component c);
	public abstract void display(int depth);
}

Leaf葉子類,由於葉子沒有子節點,無需再增葉子和樹枝,所以這裏的add和remove方法就沒有實現。但卻保留了這兩個方法,是爲了與樹枝節點Composite類保持一致的接口,以消除葉節點和枝節點在抽象層次的區別。

public class Leaf extends Component{
	 
	public Leaf(String name) {
		super(name);
	}
	
	@Override
	public void add(Component c) {
		
	}

	@Override
	public void remove(Component c) {
		
	}

	@Override
	public void display(int depth) {
		for(int i=0;i<depth;i++) {
			System.out.print("-");
		}
		System.out.println(name);
	}

}

Composite枝節點類

public class Composite extends Component{
	private List<Component> children = new ArrayList<Component>();
	
	public Composite(String name) {
		super(name);
	}
	
	@Override
	public void add(Component c) {
		children.add(c);
	}

	@Override
	public void remove(Component c) {
		if(children.contains(c)) {
			children.remove(c);
		}
	}

	@Override
	public void display(int depth) {
		for(int i=0;i<depth;i++) {
			System.out.print("-");
		}
		System.out.println(name);
		for (Component component : children) {
			component.display(depth+2);
		}
	}

}

測試類

public class CompositeTest {

	public static void main(String[] args) {
		//創建根節點
		Composite root = new Composite("Root");
		//添加兩個葉節點
		root.add(new Leaf("Leaf A"));
		root.add(new Leaf("Leaf B"));
		
		//創建枝節點
		Composite c1 = new Composite("Composite X");
		//給枝節點X添加兩個葉節點
		c1.add(new Leaf("Leaf XA"));
		c1.add(new Leaf("Leaf XB"));
		
		//將枝節點X添加到根節點上
		root.add(c1);
		
		//創建枝節點
		Composite c2 = new Composite("Composite XY");
		//給枝節點XY添加兩個葉節點
		c2.add(new Leaf("Leaf XYA"));
		c2.add(new Leaf("Leaf XYB"));
		
		//將枝節點XY添加到枝節點X上
		c1.add(c2);
		
		//給根節點添加葉節點C
		root.add(new Leaf("Leaf C"));
		
		//創建葉節點D
		Leaf d = new Leaf("Leaf D");
		//將葉節點D添加到根節點上
		root.add(d);
		//將葉節點D從根節點移除
		root.remove(d);
		
		//顯示樹狀結構結果
		root.display(1);
	}

}

結果:

用一張圖來表示這個結果的話,如下

進一步說明,這個例子裏的display方法只是爲了輸出結果顯示出比較明確的層次結構,實際應用中可以根據需求做相應調整。

三、透明方式與安全方式

1. 透明方式

 上面例子裏,葉節點Leaf類裏面的add和remove方法其實是無意義的方法,沒有實現內容,這種方式叫做透明方式。

即,在Component接口中聲明所有用來管理子對象的方法,包括add和remove等,這樣實現Component接口的所有子類都具備了add和remove方法。

這樣做的優點,上面也提到過了,就是讓枝節點和葉節點對外界來說沒有區別,具備完全一致的行爲接口。

相應的缺點就是,葉節點實現了完全沒有任何意義的add和remove方法。

2. 安全方式

與透明方式相對應,如果葉節點Leaf類中不用add和remove方法,這種方式就叫做安全方式。

即,在Component接口中不聲明add和remove方法,而是在子部件Composite中聲明所有用來管理子類對象的方法。

這樣做的優點就是上面提到的透明方式的缺點,這裏不會在Leaf類中出現無意義的方法。

相應的缺點也很明顯,就是在客戶端調用時需要做判斷進行區分,稍稍麻煩一些。

四、實戰應用

這裏就用上面的公司結構來舉例說明好了

抽象公司類Company,就是上面的Component

public abstract class Company {
	protected String name;
	
	public Company(String name) {
		this.name = name;
	}
	
	public abstract void add(Company company);
	public abstract void remove(Company company);
	public abstract void display(int depth);
	public abstract void duty();
}

具體公司實現類ConcreteCompany,就是上面的Composite

public class ConcreteCompany extends Company{
	//聲明公司集合
	private List<Company> companies = new ArrayList<Company>();
	
	public ConcreteCompany(String name) {
		super(name);
	}
	
	/**
	 * 添加子公司
	 */
	@Override
	public void add(Company company) {
		companies.add(company);
	}

	/**
	 * 刪除子公司
	 */
	@Override
	public void remove(Company company) {
		if (companies.contains(company)) {
			companies.remove(company);
		}
	}

	/**
	 * 顯示
	 */
	@Override
	public void display(int depth) {
		for(int i=0;i<depth;i++) {
			System.out.print("-");
		}
		System.out.println(name);
		for (Company company : companies) {
			company.display(depth+2);
		}		
	}

	/**
	 * 公司職責
	 */
	@Override
	public void duty() {
		for (Company company : companies) {
			company.duty();
		}		
	}

}

人力部HRCompany,就是上面的葉子類Leaf

/**
 * 人力資源部
 */
public class HRDepartment extends Company{

	public HRDepartment(String name) {
		super(name);
	}

	@Override
	public void add(Company company) {
		
	}

	@Override
	public void remove(Company company) {
		
	}

	@Override
	public void display(int depth) {
		for(int i=0;i<depth;i++) {
			System.out.print("-");
		}
		System.out.println(name);
	}

	@Override
	public void duty() {
		System.out.println(name+"職責:招聘人才");
	}

}

財務部FinanceCompany,就是上面的Leaf。這個例子中有兩個Leaf——人力部和財務部,它們都沒有子節點,符合葉子特徵,但是由於這裏人力部和財務部具有不同的公司職責,所以分開兩個實現。

/**
 * 財務部
 */
public class FinanceDepartment extends Company{

	public FinanceDepartment(String name) {
		super(name);
	}

	@Override
	public void add(Company company) {
		
	}

	@Override
	public void remove(Company company) {
		
	}

	@Override
	public void display(int depth) {
		for(int i=0;i<depth;i++) {
			System.out.print("-");
		}
		System.out.println(name);
	}

	@Override
	public void duty() {
		System.out.println(name+"職責:公司財務管理");
	}

}

測試類

public class CompositeTest {

	public static void main(String[] args) {
		ConcreteCompany root = new ConcreteCompany("北京總公司");
		
		root.add(new HRDepartment("總公司人力部"));
		root.add(new FinanceDepartment("總公司財務部"));
		
		ConcreteCompany c1 = new ConcreteCompany("上海分公司");
		c1.add(new HRDepartment("上海分公司人力部"));
		c1.add(new FinanceDepartment("上海分公司財務部"));
		
		root.add(c1);
		
		ConcreteCompany c2 = new ConcreteCompany("南京辦事處");
		c2.add(new HRDepartment("南京辦事處人力部"));
		c2.add(new FinanceDepartment("南京辦事處財務部"));
		
		c1.add(c2);
		
		ConcreteCompany c3 = new ConcreteCompany("杭州辦事處");
		c3.add(new HRDepartment("杭州辦事處人力部"));
		c3.add(new FinanceDepartment("杭州辦事處財務部"));
		
		c1.add(c3);
		
		System.out.println("各公司名稱:");
		root.display(1);
		
		System.out.println("\n各部門職責:");
		root.duty();
	}

}

結果

五、總結

使用場合:

(1)如果各對象體現出“部分-整體”層次結構(或樹形結構)時,可以考慮使用組合模式。

(2)當客戶希望忽略單個對象與組合對象的區別,可以使用統一接口調用時,可以考慮使用組合模式。

組合模式,簡單易用,核心其實就是一個遞歸和組合,展開來說就是一個很複雜的對象由許多基本對象構成,其中基本對象又可以構成稍複雜一些的組合對象,而組合對象又可以被重組成更復雜的組合對象,如此遞歸下去就構成了最終的對象。

 

寫在最後,

本文主要是小貓看了《大話設計模式》的一些記錄筆記,再加之自己的一些理解整理出此文,方便以後查閱,僅供參考。

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