組合模式

📢 本文由 gemini-2.5-flash 翻譯

Composite Pattern 物件結構型模式

目的

將物件組合成樹狀結構,以表示「部分 - 整體」的層級結構。組合模式讓使用者對個別物件與組合物件的使用方式保持一致性。

結構

組合模式

其中:

  • Component 為組合中的物件宣告介面;在適當情況下實作所有類別共有介面的預設行為;宣告一個介面用於存取及管理 Component 的子元件;(可選) 在遞迴結構中定義一個介面,用於存取一個父元件,並在合適的情況下實作它。
  • Leaf 在組合中表示葉節點物件,葉節點沒有子節點;在組合中定義圖元物件的行為。
  • Composite 定義所有子元件的那些元件的行為;儲存子元件;在 Component 介面中實作與子元件相關的操作。
  • Client 透過 Component 介面操控組合元件的物件。

適用性

Composite 模式適用於:

  • 想表示物件的部分 - 整體層級結構。
  • 希望使用者忽略組合物件與個別物件的不同,使用者將統一地使用組合結構中的所有物件。

範例 1

某公司的組織結構圖如下圖所示:

組合模式-範例1-圖1

現在採用組合(Composition)設計模式來建構該公司的組織結構,得到如下圖所示的類別圖:

組合模式-範例1-圖2

其中 Company 為抽象類別,定義了在組織結構圖上新增(Add)和刪除(Delete)分公司/辦事處或者部門的方法介面。類別 ConcreteCompany 表示具體的分公司或者辦事處,分公司或辦事處下可以設定不同的部門。類別 HRDepartment 和 FinanceDepartment 分別表示人力資源部和財務部。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.*;

abstract class Company {
    protected String name;
    public Company(String name) {   this.name   = name; }
    public abstract void Add(Company c); // 增加子公司、辦事處或部門
    public abstract void Delete(Company c); // 刪除子公司、辦事處或部門
}

class ConcreteCompany extends Company {
    private List<Company> children = new ArrayList<Company>();
    // 儲存子公司、辦事處或部門
    public ConcreteCompany(String name) { super(name); }
    public void Add(Company c) {children.add(c); }
    public void Delete(Company c) {children.remove(c); }
}

class HRDepartment extends Company {
    public HRDepartment(String name) { super(name); }
// 其他程式碼省略
}

class FinanceDepartment extends Company {
    public FinanceDepartment(String name) { super(name); }
// 其他程式碼省略
}

public class Test {
    public static void main(String[] args) {
        ConcreteCompany root = new ConcreteCompany("北京總公司");
        root.Add(new HRDepartment("總公司人力資源部"));
        root.Add(new FinanceDepartment("總公司財務部"));

        ConcreteCompany comp = new ConcreteCompany("上海分公司");
        comp.Add(new HRDepartment("上海分公司人力資源部"));
        comp.Add(new FinanceDepartment("上海分公司財務部"));
        root.Add(comp);

        ConcreteCompany comp1 = new ConcreteCompany("南京辦事處");
        comp1.Add(new HRDepartment("南京辦事處人力資源部"));
        comp1.Add(new FinanceDepartment("南京辦事處財務部"));
        comp.Add(comp1); // 其他程式碼省略
    }
}

範例 2

層疊選單是視窗風格的軟體系統中經常採用的一種系統功能組織方式。層疊選單中包含的可能是一個選單項目(直接對應某個功能),也可能是一個子選單,現在採用組合(composite)設計模式實作層疊選單,得到如下圖所示的類別圖:

組合模式-範例2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.*;

abstract class MenuComponent {  // 構成層疊選單的元素
    protected String name;      // 選單項目或子選單名稱
    public void printName() { System.out.println(name); }
    public abstract boolean addMenuElement(MenuComponent element) ;
    public abstract boolean removeMenuElement(MenuComponent element);
    public abstract List<MenuComponent> getElement();
}

class MenuItem extends MenuComponent {
    public MenuItem(String name) { this.name=name; }
    public boolean addMenuElement(MenuComponent element) { return false; }
    public boolean removeMenuElement(MenuComponent element) {
        return false;
    }
    public List<MenuComponent> getElement(){ return null; }
}

class Menu extends MenuComponent { // 修正 MemuComponent 為 MenuComponent
    private   List<MenuComponent> elementsList;
    public Menu(String name) {
        this.name = name;
        this.elementsList = new ArrayList<MenuComponent>(); // 修正 elementList
    }
    public boolean addMenuElement(MenuComponent element) {
        return elementsList.add(element); // 修正 elementList
    }
    public boolean removeMenuElement(MenuComponent element) {
        return elementsList.remove(element); // 修正 elementList
    }
    public List<MenuComponent> getElement() { return elementsList; } // 修正 elementList
}

class CompositeTest {
    public static void main(String[] args) {
        MenuComponent mainMenu = new Menu("Insert");
        MenuComponent subMenu = new Menu("Chart");
        MenuComponent element = new MenuItem("On This Sheet");
        mainMenu.addMenuElement(subMenu);
        subMenu.addMenuElement(element);
        printMenus(mainMenu);
    }

    private static void printMenus(MenuComponent ifile) {
        ifile.printName();
        List<MenuComponent> children = ifile.getElement();
        if (children == null) return;
        for(MenuComponent element : children) { // 修正 for 迴圈語法
            printMenus(element);
        }
    }
}

範例 3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import java.util.*;
public class CompositePattern {
    public static void main(String[] args) {
        AbstractFile root = new Folder("root");

        AbstractFile bin = new Folder("bin");
        AbstractFile tmp = new Folder("tmp");
        AbstractFile file = new File("file");

        root.Add(bin);
        bin.Add(tmp);
        root.Add(file);

        // root.Remove(tmp);
        print(root);
    }

    static void print(AbstractFile file){
        List<AbstractFile> lf = file.GetChildren();

        file.Operation();

        // 更簡潔的方法
        // if(lf == null) return;
        // for(AbstractFile i : lf) print(i);

        if (lf != null) {
            for (AbstractFile i : lf) {
                if (i != null) {
                    print(i);
                }
                else
                    return;
            }
        }
        else
            return;
    }
}

abstract class AbstractFile{
    protected String name;

    public void Operation(){
        System.out.println(name);
    }

    public abstract boolean Add(AbstractFile af);
    public abstract boolean Remove(AbstractFile af);
    public abstract List<AbstractFile> GetChildren();
}

class Folder extends AbstractFile{
    private List<AbstractFile> childrenList = new ArrayList<>();

    public Folder(String name){
        this.name = name;
    }

    @Override
    public boolean Add(AbstractFile af){
        return childrenList.add(af);
    }

    @Override
    public boolean Remove(AbstractFile af){
        return childrenList.remove(af);
    }

    @Override
    public List<AbstractFile> GetChildren(){
        return this.childrenList;
    }
}

class File extends AbstractFile{
    public File(String name){
        this.name = name;
    }

    @Override
    public boolean Add(AbstractFile af){
        System.out.println("Forbidden");
        return false;
    }

    @Override
    public boolean Remove(AbstractFile af){
        System.out.println("Forbidden");
        return false;
    }

    @Override
    public List<AbstractFile> GetChildren(){
        return null;
    }
}