1. 程式人生 > 實用技巧 >設計模式——組合模式

設計模式——組合模式

生活中存在很多 “部分-整體” 的關係,例如:大學中的學校與學院、學院與專業的關係。高樓與樓層和房間之間的關係等等。在軟體開發中也有類似的情況。這些簡單物件與複合物件之間的關係,如果用組合模式(把學校、院、系都看作是組織結構,他們之間沒有繼承的關係,而是一種樹形結構,可以更好的實現管理操作)來實現會很方便。

一、基本介紹


1)、組合模式(Composite Pattern):又叫部分整體模式,它建立了物件組的樹形結構,將物件組合成樹狀結構以表示“整體-部分”的層次關係。
2)、組合模式依據樹形結構來組合物件,用來表示部分以及整體層次。
3)、這種設計模式屬於結構型模式。
4)、組合模式使得使用者對單個物件和組合物件的訪問具有一致性,即:組合能讓客戶以一致的方式處理個別對象以及組合物件。
5)、優點:組合模式使得客戶端程式碼可以一致地處理單個物件和組合物件,無需關係自己處理的是單個物件,還是組合物件,這簡化了客戶端程式碼;更容易在組合體內加入新的物件,客戶端不會因為加入了新的物件而更改原始碼,滿足 "開閉原則 OCP"。
6)、缺點:設計複雜,客戶端需要花更多時間理清類之間的層次關係。不容易限制容器中構建,不容易用繼承的方式增加構建的新功能。

二、組合模式——結構類圖


組合模式分為透明式的組合模式和安全式組合模式:
1)、透明方式:在該方法中,由於抽象構建聲明瞭所有子類中的全部方法,所以客戶端無需區別樹葉物件和樹枝物件。對客戶端來說是透明的。其缺點是:樹葉構件本來沒有 Add()、Remove() 及 GetChild() 方法,卻要實現它們(空實現或拋異常),這樣會帶來一些安全性問題。
2)、安全方式:在該方式中,將管理子構件的方法移到樹枝構件中,抽象構件和樹葉構件沒有對子物件的管理方法,這樣就避免了上一種方式的安全性問題,但由於葉子和分支有不同的介面,客戶端在呼叫時要知道樹葉物件和樹枝物件的存在,所以失去了透明性。

三、組合模式程式碼案例分析


【1】抽象構件(Component)角色:主要作用是為樹葉和樹枝構件生命公共介面,並實現他們的預設行為。在透明式的組合模式中,抽象構件還宣告訪問和管理子類的介面(add/remove)。在安全式的組合模式中不宣告訪問和管理子類的介面,管理工作由樹枝構建完成。

 1 public abstract class AbstractComponent {
 2     //學校的名稱
 3     private String name;
 4     //備註
 5     private String remark;
 6     //定義一個輸入的抽象方法
 7     public abstract
void output(); 8 9 //非葉子節點都具有的增加和刪除方法 10 public void add(AbstractComponent abstractComponent) { 11 //當重寫此方法直接呼叫時,就會丟擲此異常。與Arrays.asList()內部類中的add與remove寫法一致 12 throw new UnsupportedOperationException(); 13 } 14 public void remove(AbstractComponent abstractComponent) { 15 throw new UnsupportedOperationException(); 16 } 17 18 //構造器 19 public AbstractComponent(String name, String remark) { 20 super(); 21 this.name = name; 22 this.remark = remark; 23 } 24 //get/set方法 tostring方法 略 25 } 26 }

【2】樹枝構件(Composite)角色:是組合中的分支節點物件,它有子節點。它實現了抽象構件角色中宣告的介面,它的主要作用是

 1 public class University extends AbstractComponent{
 2     //構造器
 3     public University(String name, String remark) {
 4         super(name, remark);
 5     }
 6     //大學中包含多個學院
 7     List<AbstractComponent> college = new ArrayList<AbstractComponent>();
 8     //重寫輸出方法:輸出葉子節點
 9     @Override
10     public void output() {
11         System.out.println("===="+super.getName()+"====");
12         for (AbstractComponent abstractComponent : college) {
13             abstractComponent.output();
14         }
15     }
16     //組合類也就是樹枝節點,需要重寫add與remove方法
17     @Override
18     public void add(AbstractComponent abstractComponent) {
19         college.add(abstractComponent);
20     }
21     @Override
22     public void remove(AbstractComponent abstractComponent) {
23         college.remove(abstractComponent);
24     }
25 }

【3】學院類也是樹枝構件(Composite)角色,與上述基本一致。

 1 public class College extends AbstractComponent{
 2     //構造器
 3     public College(String name, String remark) {
 4         super(name, remark);
 5     }
 6     
 7     List<AbstractComponent> list = new ArrayList<>();
 8     //輸入
 9     @Override
10     public void output() {
11         for (AbstractComponent abstractComponent : list) {
12             abstractComponent.output();
13         }
14         
15     }
16     //重寫新增和刪除方法
17     @Override
18     public void add(AbstractComponent abstractComponent) {
19         list.add(abstractComponent);
20     }
21     @Override
22     public void remove(AbstractComponent abstractComponent) {
23         list.remove(abstractComponent);
24     }
25 }

【4】樹葉構件(Leaf)角色:是組合中的葉節點物件,它沒有子節點,用於實現抽象構件角色中 宣告的公共介面。

 1 public class Major extends AbstractComponent{
 2     //構造器
 3     public Major(String name, String remark) {
 4         super(name, remark);
 5     }
 6     //add , remove 就不用寫了,因為他是葉子節點
 7     //輸入
 8     @Override
 9     public void output() {
10         System.out.println(getName());
11     }
12 
13 }

【5】測試類

 1 public class Client {
 2     public static void main(String[] args) {
 3         //定義一個大學
 4         AbstractComponent university = new University("浙江大學", "浙江人的驕傲");
 5         //定義一個學院
 6         AbstractComponent college = new College("計算機學院", "簡稱妓院");
 7         //將妓院新增至學校
 8         university.add(college);
 9         //定義一個專業
10         AbstractComponent major = new Major("電腦科學與技術", "考研大專業");
11         //新增至學院
12         college.add(major);
13         //輸出:電腦科學與技術 輸入的都是葉子節點的output方法
14         major.output();
15         college.output();
16         university.output();
17     }
18 }

四、組合模式原始碼分析


【1】HashMap 組合模式:首先定義了抽象構建角色 Map<K,V>

public interface Map<K,V> {....}

【2】使用介面介面卡模式,定義了抽象實現:AbstractMap

1 public abstract class AbstractMap<K,V> implements Map<K,V> {
2         public V put(K key, V value) {
3         //由子類實現
4         throw new UnsupportedOperationException();
5     }
6     ......
7 }

【3】HashMap 等實現類屬於樹枝構建角色:組合了 Node 葉子節點類

 1 public class HashMap<K,V> extends AbstractMap<K,V>
 2     implements Map<K,V>, Cloneable, Serializable {
 3     public V put(K key, V value) {
 4         return putVal(hash(key), key, value, false, true);
 5     }
 6     public void putAll(Map<? extends K, ? extends V> m) {
 7         putMapEntries(m, true);
 8     }
 9     ......
10     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
11                    boolean evict) {
12         Node<K,V>[] tab; Node<K,V> p; int n, i;
13         ......
14     }
15 }

【4】Node 類是上述類的內部類,也就是組合模式中的樹葉構建:陣列儲存時,通過 put 方法存入 Node 物件中,葉子節點 Node 中不包含新增方法等,只包含一些屬性和其 get/set 方法

 1  public class HashMap<K,V> extends AbstractMap<K,V>
 2     implements Map<K,V>, Cloneable, Serializable {
 3     ......
 4     static class Node<K,V> implements Map.Entry<K,V> {
 5         final int hash;
 6         final K key;
 7         V value;
 8         Node<K,V> next;
 9 
10         Node(int hash, K key, V value, Node<K,V> next) {
11             this.hash = hash;
12             this.key = key;
13             this.value = value;
14             this.next = next;
15         }
16 
17         public final K getKey()        { return key; }
18         public final V getValue()      { return value; }
19         public final String toString() { return key + "=" + value; }
20 
21      public final int hashCode() {
22             return Objects.hashCode(key) ^ Objects.hashCode(value);
23         }
24 
25         public final V setValue(V newValue) {
26             V oldValue = value;
27             value = newValue;
28             return oldValue;
29         }
30 
31         public final boolean equals(Object o) {
32             if (o == this)
33                 return true;
34             if (o instanceof Map.Entry) {
35                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
36                 if (Objects.equals(key, e.getKey()) &&
37                     Objects.equals(value, e.getValue()))
38                     return true;
39             }
40             return false;
41         }
42     }
43     ......
44 }

五、組合模式的注意事項和細節


☛ 簡化客戶端操作。客戶端只需要面對一致的物件而不用考慮整體部分或者節點葉子的問題。
☛ 具有較強的擴充套件性。當我們要更改組合物件時,我們只需要調整內部的層次關係,客戶端不用做任何改動。
☛ 方便創建出複雜的層次結構。客戶端不用理會組合裡面的組成細節,容器新增節點或者葉子。從而創建出複雜的樹形結構。
☛ 需要遍歷組織機構,或者處理的物件具有樹形結構時,非常適合使用組合模式。
☛ 當要求較高的抽象性時,如果節點和葉子有很多差異的話,例如很多方法和屬性都不一樣,不適合使用組合模式。