簡說設計模式——組合模式


一、什么是組合模式

  前面我們講過Swing(Java進階篇(六)——Swing程序設計(上)),在Swing中,容器Container和組件如Button、JLabel等等之間的關系就是組合關系,一個容器中有多個組件,組合模式更形象的說就是一棵樹,描述的時部分-整體的關系。

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

  所以當我們的案例是樹形結構或者是部分-整體的關系時,就可以考慮使用組合模式。

  組合模式有兩種不同的實現,分別為透明模式安全模式,下面將詳細說明一下兩種實現的區別。

  先說明一下UML圖中各角色的職責。Component是對象聲明接口,在適當情況下,實現所有類共有接口的默認行為;Leaf是葉子節點對象,其沒有子節點;Composite是樹枝節點對象,用來存儲部件,組合樹枝節點和葉子節點形成一個樹形結構。

  下面這兩種方式我們共用同一套客戶端,先將客戶端代碼放上。

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //創建根節點及其子節點
 5         Composite root = new Composite("root");
 6         root.add(new Leaf("Leaf A"));
 7         root.add(new Leaf("Leaf B"));
 8 
 9         //創建第二層節點及其子節點
10         Composite branch = new Composite("Composite X");
11         branch.add(new Leaf("Leaf XA"));
12         branch.add(new Leaf("Leaf XB"));
13         root.add(branch);
14         
15         //創建第三層節點及其子節點
16         Composite branch2 = new Composite("Composite XY");
17         branch2.add(new Leaf("Leaf XYA"));
18         branch2.add(new Leaf("Leaf XYB"));
19         branch.add(branch2);
20         
21         //創建第二層節點
22         root.add(new Leaf("Leaf C"));
23         
24         //創建第二層節點並刪除
25         Leaf leaf = new Leaf("Leaf D");
26         root.add(leaf);
27         root.remove(leaf);
28         
29         //打印
30         root.display(1);
31     }
32     
33 }

二、組合模式之透明模式

  透明模式是把組合使用的方法放到抽象類中,不管葉子對象還是樹枝對象都有相同的結構,這樣做的好處就是葉子節點和樹枝節點對於外界沒有區別,它們具備完全一致的行為接口。但因為Leaf類本身不具備add()、remove()方法的功能,所以實現它是沒有意義的。UML結構圖如下:

  1. Component

 1 public abstract class Component {
 2     
 3     protected String name;
 4     
 5     public Component(String name) {
 6         this.name = name;
 7     }
 8 
 9     //增加一個葉子構件或樹枝構件
10     public abstract void add(Component component);
11     
12     //刪除一個葉子構件或樹枝構件
13     public abstract void remove(Component component);
14     
15     //獲取分支下的所有葉子構件和樹枝構件
16     public abstract void display(int depth);
17     
18 }

  2. Composite

 1 public class Composite extends Component {
 2 
 3     public Composite(String name) {
 4         super(name);
 5     }
 6 
 7     //構建容器
 8     private ArrayList<Component> componentArrayList = new ArrayList<Component>();
 9     
10     @Override
11     public void add(Component component) {
12         this.componentArrayList.add(component);
13     }
14 
15     @Override
16     public void remove(Component component) {
17         this.componentArrayList.remove(component);
18     }
19 
20     @Override
21     public void display(int depth) {
22         //輸出樹形結構
23         for(int i=0; i<depth; i++) {
24             System.out.print('-');
25         }
26         System.out.println(name);
27         
28         //下級遍歷
29         for (Component component : componentArrayList) {
30             component.display(depth + 1);
31         }
32     }
33 
34 }

  3. Leaf

 1 public class Leaf extends Component {
 2 
 3     public Leaf(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void add(Component component) {
 9         //空實現,拋出“不支持請求”異常
10         throw new UnsupportedOperationException();
11     }
12 
13     @Override
14     public void remove(Component component) {
15         //空實現,拋出“不支持請求”異常
16         throw new UnsupportedOperationException();
17     }
18 
19     @Override
20     public void display(int depth) {
21         //輸出樹形結構的葉子節點
22         for(int i=0; i<depth; i++) {
23             System.out.print('-');
24         }
25         System.out.println(name);
26     }
27 
28 }

  通過組合模式輸出一個樹形結構,運行結果如下:

三、組合模式之安全模式

  安全模式是把樹枝節點和樹葉節點徹底分開,樹枝節點單獨擁有用來組合的方法,這種方法比較安全。但由於不夠透明,所以樹葉節點和樹枝節點將不具有相同的接口,客戶端的調用需要做相應的判斷,帶來了不便。UML結構圖如下:

  1. Component

  這里相比透明模式就少了add()和romove()抽象方法的聲明。

 1 public abstract class Component {
 2     
 3     protected String name;
 4     
 5     public Component(String name) {
 6         this.name = name;
 7     }
 8 
 9     //獲取分支下的所有葉子構件和樹枝構件
10     public abstract void display(int depth);
11     
12 }

  2. Composite

  這里add()和remove()方法的實現就從繼承變為了自己實現。

 1 public class Composite extends Component {
 2 
 3     public Composite(String name) {
 4         super(name);
 5     }
 6 
 7     //構建容器
 8     private ArrayList<Component> componentArrayList = new ArrayList<Component>();
 9     
10     //增加一個葉子構件或樹枝構件
11     public void add(Component component) {
12         this.componentArrayList.add(component);
13     }
14 
15     //刪除一個葉子構件或樹枝構件
16     public void remove(Component component) {
17         this.componentArrayList.remove(component);
18     }
19 
20     @Override
21     public void display(int depth) {
22         //輸出樹形結構
23         for(int i=0; i<depth; i++) {
24             System.out.print('-');
25         }
26         System.out.println(name);
27         
28         //下級遍歷
29         for (Component component : componentArrayList) {
30             component.display(depth + 1);
31         }
32     }
33 
34 }

  3. Leaf

  葉子節點中沒有了空實現,比較安全。

 1 public class Leaf extends Component {
 2 
 3     public Leaf(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void display(int depth) {
 9         //輸出樹形結構的葉子節點
10         for(int i=0; i<depth; i++) {
11             System.out.print('-');
12         }
13         System.out.println(name);
14     }
15 
16 }

  運行結果如下:

  由此可看出兩個方法是相同的運行結果,區別在於內部實現不同,一種是葉節點與樹枝節點具備一致的行為接口但有空實現的透明模式,另一種是樹枝節點單獨擁有用來組合的方法但調用不便的安全模式。

  為什么說它調用不便呢,因為我們如果通過遞歸遍歷樹時,這時需要判斷當前節點是葉子節點還是樹枝節點,客戶端就需要相應的判斷。

四、組合模式的應用

  1. 何時使用

  • 想表達“部分-整體”層次結構(樹形結構)時
  • 希望用戶忽略組合對象與單個對象的不同,用戶將統一的使用組合結構中的所有對象

  2. 方法

  • 樹枝和葉子實現統一接口,樹枝內部組合該接口

  3. 優點

  • 高層模塊調用簡單。一棵樹形機構中的所有節點都是Component,局部和整體對調用者來說沒有任何區別,高層模塊不必關心自己處理的是單個對象還是整個組合結構。
  • 節點自由增加

  4. 缺點

  • 使用組合模式時,其葉子和樹枝的聲明都是實現類,而不是接口,違反了依賴倒轉原則

  5. 使用場景

  • 維護和展示部分-整體關系的場景(如樹形菜單、文件和文件夾管理)
  • 從一個整體中能夠獨立出部分模塊或功能的場景

  6. 應用實例

  • Swing中,Button、Checkbox等組件都是樹葉,而Container容器是樹枝
  • 文本編輯時,可以單個字編輯,也可以整段編輯,還可以全文編輯
  • 文件復制時,可以一個一個文件復制,也可以整個文件夾復制

五、組合模式的實現

  下面我們以公司的層級結構為例,先看一下這個例子中該公司的層級結構(該例選自大話設計模式——程傑著)。

  這種部分與整體的關系,我們就可以考慮使用組合模式,下面采用組合模式的透明模式對其實現,UML圖如下:

  1. 具體公司類

  此為樹枝節點,實現添加、移除、顯示和履行職責四種方法。

 1 public class ConcreteCompany extends Company {
 2 
 3     private List<Company> companyList = new ArrayList<Company>();
 4     
 5     public ConcreteCompany(String name) {
 6         super(name);
 7     }
 8 
 9     @Override
10     public void add(Company company) {
11         this.companyList.add(company);
12     }
13 
14     @Override
15     public void remove(Company company) {
16         this.companyList.remove(company);
17     }
18 
19     @Override
20     public void display(int depth) {
21         //輸出樹形結構
22         for(int i=0; i<depth; i++) {
23             System.out.print('-');
24         }
25         System.out.println(name);
26         
27         //下級遍歷
28         for (Company component : companyList) {
29             component.display(depth + 1);
30         }
31     }
32 
33     @Override
34     public void lineOfDuty() {
35         //職責遍歷
36         for (Company company : companyList) {
37             company.lineOfDuty();
38         }
39     }
40 
41 }

  2. 人力資源部

  葉子節點,add和remove方法空實現。

 1 public class HRDepartment extends Company {
 2 
 3     public HRDepartment(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void add(Company company) {
 9         
10     }
11 
12     @Override
13     public void remove(Company company) {
14         
15     }
16 
17     @Override
18     public void display(int depth) {
19         //輸出樹形結構的子節點
20         for(int i=0; i<depth; i++) {
21             System.out.print('-');
22         }
23         System.out.println(name);
24     }
25 
26     @Override
27     public void lineOfDuty() {
28         System.out.println(name + " : 員工招聘培訓管理");
29     }
30     
31 }

  3. 財務部

  葉子節點,add和remove方法空實現。

 1 public class FinanceDepartment extends Company {
 2     
 3     public FinanceDepartment(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void add(Company company) {
 9         
10     }
11 
12     @Override
13     public void remove(Company company) {
14         
15     }
16 
17     @Override
18     public void display(int depth) {
19         //輸出樹形結構的子節點
20         for(int i=0; i<depth; i++) {
21             System.out.print('-');
22         }
23         System.out.println(name);
24     }
25 
26     @Override
27     public void lineOfDuty() {
28         System.out.println(name + " : 公司財務收支管理");
29     }
30     
31 }

  4. Client客戶端

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //總公司
 5         ConcreteCompany root = new ConcreteCompany("北京總公司");
 6         root.add(new HRDepartment("總公司人力資源部"));
 7         root.add(new FinanceDepartment("總公司財務部"));
 8         
 9         //分公司
10         ConcreteCompany company = new ConcreteCompany("上海華東分公司");
11         company.add(new HRDepartment("華東分公司人力資源部"));
12         company.add(new FinanceDepartment("華東分公司財務部"));
13         root.add(company);
14         
15         //辦事處
16         ConcreteCompany company1 = new ConcreteCompany("南京辦事處");
17         company1.add(new HRDepartment("南京辦事處人力資源部"));
18         company1.add(new FinanceDepartment("南京辦事處財務部"));
19         company.add(company1);
20         
21         ConcreteCompany company2 = new ConcreteCompany("杭州辦事處");
22         company2.add(new HRDepartment("杭州辦事處人力資源部"));
23         company2.add(new FinanceDepartment("杭州辦事處財務部"));
24         company.add(company2);
25         
26         System.out.println("結構圖:");
27         root.display(1);
28         
29         System.out.println("\n職責:");
30         root.lineOfDuty();
31     }
32     
33 }

  運行結果如下:

  組合模式這樣就定義了包含人力資源部和財務部這些基本對象和分公司、辦事處等組合對象的類層次結構。

  基本對象可以被組合成更復雜的組合對象,而這個組合對象又可以被組合,這樣不斷地遞歸下去,客戶代碼中,任何用到基本對象的地方都可以使用組合對象了。

  這里用了透明模式,用戶不用關心到底是處理一個葉節點還是處理一個組合組件,也就用不着為定義組合而寫一些選擇判斷語句了。簡單點說就是組合模式可以讓客戶一致地使用組合結構和單個對象。

 

  源碼地址:https://gitee.com/adamjiangwh/GoF


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM