將對象組合成樹形結構來表現出「總體/部分」的層次結構。組合能讓客戶以一致性的方式處理個別的對象以及對象組合。windows
抽象組件(Component): 爲組合中的對象(節點或者組件)聲明接口,也可提供默認的接口缺省實現;api
節點對象(Leaf): 組合中的葉節點對象,葉節點對象再也不擁有子節點;安全
複合對象(Composite):容許存在多個子部件,須要包含的子部件,並實現對子部件的操做接口;bash
客戶端(Client): 調用端,經過調用Component相關接口,操做組合中的對象;微信
透明模式:葉節點和組合對象所擁有的操做都放抽象組件Component,這樣客戶端調用時,不須要判斷節點類型均可以進行api調用,無需類型轉換。可是對應的存在安全性的問題,由於葉節點自己並不具有組合對象add、remove、get等等有關子節點的操做api,這樣的設計可能致使調用後出現與預期不符的現象,由於客戶有可能會作一些無心義 的事情,例如在Leaf 中增長和刪除對象等。框架
安全模式:將組合對象獨有的api操做都放在對應的實現類中,好處就是安全性提高,只有組合對象纔會提供add、remove、get等操做。缺點就是不夠透明,整個樹形結構元素使用上,由於Leaf 和Composite具備不一樣的接口,客戶還得區分判斷是葉節點或者組合對象,並進行類型轉換才能夠調用相應的api;ide
2種方式在於透明性和安全性的互換,這須要在安全性和透明性之間作出權衡選擇,在這一模式中,相對於安全性,咱們比較強調透明性。若是你選擇了安全性,有時你可能會丟失類型信息,而且不得不將一個組件轉換成一個組合。這樣的類型轉換一定不是類型安全的。post
這邊以透明模式爲例:ui
將默認實現爲拋出異常(也可空實現之類的,具體狀況具體分析),子類根據須要進行重寫this
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();
}
}
複製代碼
public class Leaf extends Component{
@Override
public void operation() {
//...
System.out.println("葉節點自身的操做");
}
}
複製代碼
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不須要關係是建立的什麼對象
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等相關文件操做。
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();
}
}
複製代碼
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;
}
}
複製代碼
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) {
//....
}
}
複製代碼
使得使用方可以以一致性的方式調用接口,來處理對象,而沒必要關心這個對象是單個葉節點仍是一個組合的對象結構。
安全性與透明性的考慮
有一系列對象集合,而且這些對象集合有明顯的"總體/部分"的關係,能夠構建成樹形結構。