天天看點

設計模式之路 | 組合模式

組合模式的定義與特點

組合(Composite)模式的定義:有時又叫作部分-整體模式,它是一種将對象組合成樹狀的層次結構的模式,用來表示“部分-整體”的關系,使使用者對單個對象群組合對象具有一緻的通路性。

組合模式的主要優點有:

  1. 組合模式使得用戶端代碼可以一緻地處理單個對象群組合對象,無須關心自己處理的是單個對象,還是組合對象,這簡化了用戶端代碼;
  2. 更容易在組合體内加入新的對象,用戶端不會因為加入了新的對象而更改源代碼,滿足“開閉原則”;

其主要缺點是:

  1. 設計較複雜,用戶端需要花更多時間理清類之間的層次關系;
  2. 不容易限制容器中的構件;
  3. 不容易用繼承的方法來增加構件的新功能;

組合模式的結構與實作

組合模式的結構不是很複雜,下面對它的結構和實作進行分析。

1. 模式的結構

組合模式包含以下主要角色。

  1. 抽象構件(Component)角色:它的主要作用是為樹葉構件和樹枝構件聲明公共接口,并實作它們的預設行為。在透明式的組合模式中抽象構件還聲明通路和管理子類的接口;在安全式的組合模式中不聲明通路和管理子類的接口,管理工作由樹枝構件完成。
  2. 樹葉構件(Leaf)角色:是組合中的葉節點對象(葉子節點),它沒有子節點,用于實作抽象構件角色中 聲明的公共接口。
  3. 樹枝構件(Composite)角色:是組合中的分支節點對象(非葉子節點),它有子節點。它實作了抽象構件角色中聲明的接口,它的主要作用是存儲和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

組合模式分為透明式的組合模式和安全式的組合模式。

(1) 透明方式:在該方式中,由于抽象構件聲明了所有子類中的全部方法,是以用戶端無須差別樹葉對象和樹枝對象,對用戶端來說是透明的。但其缺點是:樹葉構件本來沒有 Add()、Remove() 及 GetChild() 方法,卻要實作它們(空實作或抛異常),這樣會帶來一些安全性問題。其結構圖如圖 1 所示。

設計模式之路 | 組合模式

代碼實作:

/**
 * 組織結構,相當于Component接口
 * 
 * @author Administrator
 */
public abstract class OrganizationComponent {

  /**
   * 名稱
   */
  private String name;

  /**
   * 說明
   */
  private String desc;

  public OrganizationComponent(String name, String desc) {
    super();
    this.name = name;
    this.desc = desc;
  }

  /**
   * 新增方法,預設實作 這裡不寫成抽象方法,是考慮葉子節點不需要實作改方法
   */
  protected void add(OrganizationComponent component) {
    throw new UnsupportedOperationException();
  }

  protected void remove(OrganizationComponent component) {
    throw new UnsupportedOperationException();
  }

  /**
   * 列印方法,描述基本資訊
   */
  public abstract void print();

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getDesc() {
    return desc;
  }

  public void setDesc(String desc) {
    this.desc = desc;
  }

  @Override
  public String toString() {
    return "OrganizationComponent [name=" + name + ", desc=" + desc + "]";
  }

}      
/**
 * University根節點,下級為Collage,相當于Composite
 * 
 * @author Administrator
 */
public class University extends OrganizationComponent {

  /**
   * list存放的是學院Collage
   */
  private List<OrganizationComponent> organizationComponentList = new ArrayList<>();

  public University(String name, String desc) {
    super(name, desc);
  }

  @Override
  protected void add(OrganizationComponent component) {
    organizationComponentList.add(component);
  }
  
  @Override
  protected void remove(OrganizationComponent component) {
    organizationComponentList.remove(component);
  }

  @Override
  public void print() {
    System.out.println("================================");
    System.out.println(this.toString());
    // 周遊輸出學院
    for (OrganizationComponent organizationComponent : organizationComponentList) {
      organizationComponent.print();
    }
  }

  @Override
  public String toString() {
    return "University [name=" + getName() + ", desc=" + getDesc() + "]";
  }
}      
/**
 * 上級為University,下級為Department,相當于Composite
 * 
 * @author Administrator
 */
public class Collage extends OrganizationComponent {

  /**
   * 這個list存方法的是院系Department
   */
  private List<OrganizationComponent> organizationComponentList = new ArrayList<>();

  public Collage(String name, String desc) {
    super(name, desc);
  }

  @Override
  protected void add(OrganizationComponent component) {
    organizationComponentList.add(component);
  }
  
  @Override
  protected void remove(OrganizationComponent component) {
    organizationComponentList.remove(component);
  }

  @Override
  public void print() {
    System.out.println("================================");
    System.out.println(this.toString());
    // 周遊輸出學院
    for (OrganizationComponent organizationComponent : organizationComponentList) {
      organizationComponent.print();
    }
  }

  @Override
  public String toString() {
    return "Collage [name=" + getName() + ", desc=" + getDesc() + "]";
  }
}      
/**
 * Department,葉子節點,相當于Leaf
 * 不需要實作add,remove方法
 * 
 * @author Administrator
 */
public class Department extends OrganizationComponent {

  public Department(String name, String desc) {
    super(name, desc);
  }

  @Override
  public void print() {
    System.out.println(this.toString());
  }

  @Override
  public String toString() {
    return "Department [name=" + getName() + ", desc=" + getDesc() + "]";
  }
}      
public class Client {
  public static void main(String[] args) {
    
    // 建立點選節點,university
    OrganizationComponent university = new University("北京大學", "中國頂級大學");
    
    // 建立節點,collage
    OrganizationComponent collage1 = new Collage("計算機學院", "中國頂級計算機學院");
    OrganizationComponent collage2 = new Collage("古漢語學院", "中國頂級古漢語學院");
    
    // 建立葉子節點,Department
    OrganizationComponent department11 = new Department("軟體工程", "中國頂級軟體工程");
    OrganizationComponent department12 = new Department("物聯網", "中國頂級物聯網專業");
    collage1.add(department11);
    collage1.add(department12);
    university.add(collage1);
    
    OrganizationComponent department21 = new Department("八股文", "中國頂級八股文專業");
    collage2.add(department21);
    university.add(collage2);
    
    university.print();
  }
}      

運作結果:

設計模式之路 | 組合模式

(2) 安全方式:在該方式中,将管理子構件的方法移到樹枝構件中,抽象構件和樹葉構件沒有對子對象的管理方法,這樣就避免了上一種方式的安全性問題,但由于葉子和分支有不同的接口,用戶端在調用時要知道樹葉對象和樹枝對象的存在,是以失去了透明性。其結構圖如圖 2 所示。

設計模式之路 | 組合模式

2. 模式的實作

假如要通路集合 c0={leaf1,{leaf2,leaf3}} 中的元素,其對應的樹狀圖如圖 3 所示。

設計模式之路 | 組合模式

下面給出透明式的組合模式的實作代碼,與安全式的組合模式的實作代碼類似,隻要對其做簡單修改就可以了。

組合模式的應用場景

前面分析了組合模式的結構與特點,下面分析它适用的以下應用場景。

  1. 在需要表示一個對象整體與部分的層次結構的場合。
  2. 要求對使用者隐藏組合對象與單個對象的不同,使用者可以用統一的接口使用組合結構中的所有對象的場合。

組合模式的擴充

如果對前面介紹的組合模式中的樹葉節點和樹枝節點進行抽象,也就是說樹葉節點和樹枝節點還有子節點,這時組合模式就擴充成複雜的組合模式了,如 ​​Java​​ AWT/​​Swing​​ 中的簡單元件 JTextComponent 有子類 JTextField、JTextArea,容器元件 Container 也有子類 Window、Panel。複雜的組合模式的結構圖如圖 5 所示。