組合模式(Composite)是針對由多個節點對象(部分)組成的樹形結構的對象(整體)而發展出的一種結構型設計模式,它能夠使客戶端在操作整體對象或者其下的每個節點對象時做出統一的響應,保證樹形結構對象使用方法的一致性,使客戶端不必關注對象的整體或部分,最終達到對象復雜的層次結構與客戶端解耦的目的。
組合模式的核心思想是將對象看作是一個樹形結構,其中每個節點可以是一個單獨的對象(葉子節點)或者一個包含其他節點的容器(組合節點)。葉子節點和組合節點都實現了相同的接口,這樣客戶端就可以對它們進行一致的操作,而不需要關心它們的具體類型。
(資料圖片僅供參考)
組合模式有以下幾個角色:
Component(組件接口):所有復合節點與葉節點的高層抽象,定義出需要對組件操作的接口標準。對應本章例程中的抽象節點類,具體使用接口還是抽象類需根據具體場景而定。Composite(復合組件):包含多個子組件對象(可以是復合組件或葉端組件)的復合型組件,并實現組件接口中定義的操作方法。對應本章例程中作為“根節點/枝節點”的文件夾類。Leaf(葉端組件):不包含子組件的終端組件,同樣實現組件接口中定義的操作方法。對應本章例程中作為“葉節點”的文件類。Client(客戶端):按所需的層級關系部署相關對象并操作組件接口所定義的接口,即可遍歷樹結構上的所有組件。好處和壞處組合模式的好處有:
可以將對象組合成樹形結構,表示整體-部分的層次關系,符合人們的直覺。可以統一處理單個對象和對象組合,簡化了客戶端的代碼邏輯,提高了系統的可復用性。可以遵循開閉原則,擴展性高,增加新的節點類型時不需要修改原有代碼。組合模式的壞處有:
可以使設計變得過于抽象,不利于理解和維護。可以違反單一職責原則,讓葉子節點和組合節點具有相同的接口,導致葉子節點出現不必要的方法。可以導致遞歸調用過深,影響系統的性能。應用場景組合模式是一種將對象組合成樹形結構的設計模式,它可以表示整體-部分的層次關系,并且提供了一致的接口來操作單個對象和對象組合。應用場景有:
當需要表示一個對象整體與部分的層次結構時,可以使用組合模式來實現樹形結構。例如,文件系統中的文件與文件夾、組織機構中的部門與員工、商品分類中的類別與商品等。當需要統一處理單個對象和對象組合時,可以使用組合模式來實現多態性。例如,圖形界面中的簡單控件與容器控件、菜單系統中的菜單項與子菜單、報表系統中的單元格與表格等。當需要將對象的創建和使用分離時,可以使用組合模式來實現依賴注入。例如,Spring框架中的Bean對象與BeanFactory對象、測試框架中的測試用例與測試套件等。Java 代碼示例假設我們有一個文件系統,其中有兩種類型的文件:文本文件和文件夾。文本文件是葉子節點,文件夾是組合節點,可以包含其他文件。我們想要使用組合模式來實現文件系統的層次結構,并且提供一個打印文件路徑的方法。代碼如下:
定義抽象組件
public interface File { // 獲取文件名稱 String getName(); // 添加子文件 void add(File file); // 刪除子文件 void remove(File file); // 獲取子文件 List getChildren(); // 打印文件路徑 void printPath(int space);} 定義葉子節點
public class TextFile implements File { private String name; public TextFile(String name) { this.name = name; } @Override public String getName() { return name; } @Override public void add(File file) { throw new UnsupportedOperationException("Text file cannot add child file"); } @Override public void remove(File file) { throw new UnsupportedOperationException("Text file cannot remove child file"); } @Override public List getChildren() { throw new UnsupportedOperationException("Text file has no child file"); } @Override public void printPath(int space) { StringBuilder sp = new StringBuilder(); for (int i = 0; i < space; i++) { sp.append(" "); } System.out.println(sp + name); }} 定義組合節點
public class Folder implements File { private String name; private List children; public Folder(String name) { this.name = name; children = new ArrayList<>(); } @Override public String getName() { return name; } @Override public void add(File file) { children.add(file); } @Override public void remove(File file) { children.remove(file); } @Override public List getChildren() { return children; } @Override public void printPath(int space) { StringBuilder sp = new StringBuilder(); for (int i = 0; i < space; i++) { sp.append(" "); } System.out.println(sp + name); space += 2; for (File child : children) { child.printPath(space); } }} 客戶端代碼
public class Client { public static void main(String[] args) { // 創建一個根文件夾,并添加兩個文本文件和一個子文件夾 File root = new Folder("root"); root.add(new TextFile("a.txt")); root.add(new TextFile("b.txt")); File subFolder = new Folder("subFolder"); root.add(subFolder); // 在子文件夾中添加兩個文本文件 subFolder.add(new TextFile("c.txt")); subFolder.add(new TextFile("d.txt")); // 打印根文件夾的路徑 root.printPath(0); }}輸出結果:
root a.txt b.txt subFolder c.txt d.txtGo 代碼示例package main// importing fmt packageimport ("fmt")// IComposite interfacetype IComposite interface {perform()}// Leaflet structtype Leaflet struct {name string}// Leaflet class method performfunc (leaf *Leaflet) perform() {fmt.Println("Leaflet " + leaf.name)}// Branch structtype Branch struct {leafs []Leafletname stringbranches []Branch}// Branch class method performfunc (branch *Branch) perform() {fmt.Println("Branch: " + branch.name)for _, leaf := range branch.leafs {leaf.perform()}for _, branch := range branch.branches {branch.perform()}}// Branch class method add leafletfunc (branch *Branch) add(leaf Leaflet) {branch.leafs = append(branch.leafs, leaf)}//Branch class method addBranch branchfunc (branch *Branch) addBranch(newBranch Branch) {branch.branches = append(branch.branches, newBranch)}//Branch class method getLeafletsfunc (branch *Branch) getLeaflets() []Leaflet {return branch.leafs}// main methodfunc main() {var branch = &Branch{name: "branch 1"}var leaf1 = Leaflet{name: "leaf 1"}var leaf2 = Leaflet{name: "leaf 2"}var branch2 = Branch{name: "branch 2"}branch.add(leaf1)branch.add(leaf2)branch.addBranch(branch2)branch.perform()}輸出結果:
G:\GoLang\examples>go run composite.goBranch: branch 1Leaflet leaf 1Leaflet leaf 2Branch: branch 2Spring 代碼示例Spring 框架也可以使用組合模式來實現對象的層次結構,它提供了一個注解叫做 @Component,它可以用來標注一個類是一個組件,即一個可被Spring管理的Bean對象。@Component注解有一個屬性叫做value,它可以用來指定組件的名稱。我們可以使用 @Component注解來標注我們的文件類,然后在配置文件或注解中聲明這些組件,Spring 就會自動創建和管理這些組件對象。
假設我們有一個文件系統,其中有兩種類型的文件:文本文件和文件夾。文本文件是葉子節點,文件夾是組合節點,可以包含其他文件。我們想要使用組合模式來實現文件系統的層次結構,并且提供一個打印文件路徑的方法。我們可以使用 @Component注解來實現,代碼如下:
抽象組件不用改造
public interface File { // 獲取文件名稱 String getName(); // 添加子文件 void add(File file); // 刪除子文件 void remove(File file); // 獲取子文件 List getChildren(); // 打印文件路徑 void printPath();} 葉子節點添加 @Component("a.txt")注解
@Component("a.txt")public class TextFile implements File { private String name; public TextFile() { this.name = "a.txt"; } @Override public String getName() { return name; } @Override public void add(File file) { throw new UnsupportedOperationException("Text file cannot add child file"); } @Override public void remove(File file) { throw new UnsupportedOperationException("Text file cannot remove child file"); } @Override public List getChildren() { throw new UnsupportedOperationException("Text file has no child file"); } @Override public void printPath() { System.out.println(name); }} 組合節點添加 @Component("root")注解
@Component("root")public class Folder implements File { private String name; private List children; // 通過@Autowired注解自動注入所有類型為File的Bean對象到children集合中 @Autowired public Folder(List children) { this.name = "root"; this.children = children; } @Override public String getName() { return name; } @Override public void add(File file) { children.add(file); } @Override public void remove(File file) { children.remove(file); } @Override public List getChildren() { return children; } @Override public void printPath() { System.out.println(name); for (File child : children) { child.printPath(); } }} SpringBoot 測試代碼
@Slf4j@SpringBootTestclass SpringBootTest { @Autowired private Folder folder; @Test void test() { folder.printPath(); }}輸出結果:
roota.txt總結組合模式是一種常用的結構型設計模式,它可以將對象組合成樹形結構,并且提供了一致的接口來操作單個對象和對象組合,是一種值得學習和掌握的設計模式。
關注公眾號【waynblog】每周分享技術干貨、開源項目、實戰經驗、高效開發工具等,您的關注將是我的更新動力!
標簽: