小白設計模式:組合模式
將物件組合成樹形結構來表現出“整體/部分”的層次結構。組合能讓客戶以一致性的方式處理個別的物件以及物件組合。
主要組成
抽象元件(Component): 為組合中的物件(節點或者元件)宣告介面,也可提供預設的介面預設實現;
節點物件(Leaf): 組合中的葉節點物件,葉節點物件不再擁有子節點;
複合物件(Composite):允許存在多個子部件,需要包含的子部件,並實現對子部件的操作介面;
客戶端(Client): 呼叫端,通過呼叫Component相關介面,操作組合中的物件;
UML圖


透明模式:葉節點和組合物件所擁有的操作都放抽象元件Component,這樣客戶端呼叫時,不需要判斷節點型別都可以進行api呼叫,無需型別轉換。但是對應的存在安全性的問題,因為葉節點本身並不具備組合物件add、remove、get等等有關子節點的操作api,這樣的設計可能導致呼叫後出現與預期不符的現象,因為客戶有可能會做一些無意義 的事情,例如在Leaf 中增加和刪除物件等。
安全模式:將組合物件獨有的api操作都放在對應的實現類中,好處就是安全性提升,只有組合物件才會提供add、remove、get等操作。缺點就是不夠透明,整個樹形結構元素使用上,因為Leaf 和Composite具有不同的介面,客戶還得區分判斷是葉節點或者組合物件,並進行型別轉換才可以呼叫相應的api;
2種方式在於透明性和安全性的互換,這需要在安全性和透明性之間做出權衡選擇,在這一模式中,相對於安全性,我們比較強調透明性。如果你選擇了安全性,有時你可能會丟失型別資訊,並且不得不將一個元件轉換成一個組合。這樣的型別轉換必定不是型別安全的。
框架程式碼
這邊以透明模式為例:
抽象元件(Component):
將預設實現為丟擲異常(也可空實現之類的,具體情況具體分析),子類根據需要進行重寫
public abstract class Component { public abstract void operation(); public void add(Component component) { throw new UnsupportedOperationException(); } public void remove(Component component) { throw new UnsupportedOperationException(); } public Component get(int index) { throw new UnsupportedOperationException(); } } 複製程式碼
節點物件(Leaf):
public class Leaf extends Component{ @Override public void operation() { //... System.out.println("葉節點自身的操作"); } } 複製程式碼
複合物件(Composite):
public class Composite extends Component{ List<Component> components = new ArrayList<Component>(); @Override public void add(Component component) { components.add(component); } @Override public void remove(Component component) { components.remove(component); } @Override public Component get(int index) { return components.get(index); } @Override public void operation() { for (Component component : components) { component.operation(); } } } 複製程式碼
Client中呼叫:
//建立過程一般是對呼叫端隱藏,Client不需要關係是建立的什麼物件 Component component = new Composite(); Component leaf1 = new Leaf(); Component leaf2 = new Leaf(); component.add(leaf1); List<Component> list = new ArrayList<>(); list.add(component); list.add(leaf2); //Client只要執行自己所需要的操作就行 for (Component component : list) { component.operation(); } 複製程式碼
具體例子
熟悉的windows檔案目錄結構就可以看出是組合模式中的樹狀圖。節點可以是檔案(Leaf),也可以是目錄(Compostite),可以定義出共同的抽象元件(Component)提供介面: open、delete等相關檔案操作。
UML圖

程式碼
AbstractFile抽象檔案(Component):
public abstract class AbstractFile { String fileName; public AbstractFile(String fileName) { this.fileName = fileName; } public abstract void open(); public abstract void delete(); public abstract boolean isDirect(); public void add(AbstractFile abstractFile) { throw new UnsupportedOperationException(); } public void remove(AbstractFile abstractFile) { throw new UnsupportedOperationException(); } public AbstractFile get(int index) { throw new UnsupportedOperationException(); } } 複製程式碼
File檔案葉節點(Leaf):
public class File extends AbstractFile{ public File(String fileName) { super(fileName); } @Override public void open() { System.out.println("開啟檔案" + fileName); } @Override public void delete() { System.out.println("刪除檔案" + fileName); } @Override public boolean isDirect() { return false; } } 複製程式碼
Directory目錄節點(Composite)
public class Directory extends AbstractFile{ List<AbstractFile> files = new ArrayList<>(); public Directory(String fileName) { super(fileName); } @Override public void add(AbstractFile abstractFile) { files.add(abstractFile); } @Override public void remove(AbstractFile abstractFile) { files.remove(abstractFile); } @Override public AbstractFile get(int index) { return files.get(index); } @Override public void open() { for (AbstractFile abstractFile : files) { abstractFile.open(); } } @Override public void delete() { for (AbstractFile abstractFile : files) { abstractFile.delete(); } } @Override public boolean isDirect() { return true; } } 複製程式碼
客戶端呼叫
假設進行檔案刪除:
AbstractFile directory = new Directory("目錄"); AbstractFile file1 = new File("檔案1"); AbstractFile file2 = new File("檔案2"); directory.add(file1); List<AbstractFile> list = new ArrayList<>(); list.add(directory); list.add(file2); for (AbstractFile abstractFile : list) { abstractFile.delete(); } 複製程式碼
假設不使用組合模式
已上訴檔案目錄例子為例,則會導致在一個檔案集合中需要針對性的判斷該檔案型別:
Directory directory = new Directory("目錄"); File file1 = new File("檔案1"); File file2 = new File("檔案2"); List<Object> list = new ArrayList<>(); list.add(directory); list.add(file1); list.add(file2); for (Object object : list) { if (object instanceof File) { //.... } if (object instanceof Directory) { //.... } } 複製程式碼
總結
優點
使得使用方能夠以一致性的方式呼叫介面,來處理物件,而不必關心這個物件是單個葉節點還是一個組合的物件結構。
缺點
安全性與透明性的考慮
應用場景
有一系列物件集合,並且這些物件集合有明顯的"整體/部分"的關係,可以構建成樹形結構。