文章

组合模式 (Composite Pattern) 深度解析

组合模式 (Composite Pattern) 深度解析

🌳 组合模式 (Composite Pattern) 深度解析

1. 模式动机与定义

1.1. 模式动机:统一处理部分和整体

在许多场景中,我们需要处理树形结构部分-整体 (Part-Whole) 的层次结构。例如,文件系统中的文件和文件夹、组织结构中的员工和部门、GUI 框架中的组件和容器。

  • 问题:处理单个对象(叶子)和组合对象(树枝)的方式往往不同。如果客户端需要区分这两种类型并使用不同的 API,代码会变得复杂且僵硬。
  • 解决方案:组合模式将单个对象和组合对象都视为实现同一接口的抽象构件。这使得客户端可以忽略它们之间的差异,使用一致的方式处理组合结构中的所有对象。

1.2. 模式定义

组合模式 (Composite Pattern)

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

它是一种对象结构型模式,又叫部分整体模式

2. 模式结构与角色

组合模式的关键是定义一个抽象构件类,作为叶子和树枝的父类,并让树枝构件内部包含一组抽象构件。

角色名称职责描述对应到文件系统实例
Component (抽象构件)定义了叶子构件和树枝构件的公共接口,通常包含操作方法以及管理子构件的方法(可选)。FileComponent (接口)
Leaf (叶子构件)组合中的最小单位,不能再包含其他构件。实现 Component 定义的操作。File (文件)
Composite (树枝构件/组合构件)代表有子节点的复杂对象。实现 Component 定义的操作,并维护一个子构件的集合,提供管理子构件的方法(add(), remove(), getChildren())。Directory (文件夹)

2.1. 模式分类

根据抽象构件接口中是否包含管理子构件的方法,组合模式通常分为两种形式:

  1. 透明式 (Transparent)
    • Component 接口中声明所有管理子构件的方法 (add(), remove(), getChildren())。
    • 优点:客户端无需区分叶子和树枝,具有高度的透明性
    • 缺点:叶子构件无须管理子构件,但仍需要实现(或空实现)这些方法,牺牲了单一职责原则。
  2. 安全式 (Safe)
    • Component 接口中声明管理子构件的方法。
    • 管理方法只在 Composite 类中声明。
    • 优点:叶子构件不需要实现不相关的方法,符合单一职责原则
    • 缺点:客户端在处理时必须区分 Leaf 和 Composite,失去了透明性。

实际应用中,通常采用透明式以简化客户端代码。

3. 代码深度解析(组织结构)

我们以员工组织结构为例,员工可以是叶子(普通员工)也可以是树枝(经理)。

3.1. Java 代码示例(透明式)

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
import java.util.ArrayList;
import java.util.List;

// --- 1. 抽象构件 (Component) ---
// 定义所有员工/经理的公共接口
public abstract class EmployeeComponent {
    protected String name;
    protected String role;

    public EmployeeComponent(String name, String role) {
        this.name = name;
        this.role = role;
    }

    public abstract void displayDetails();

    // 管理子构件的方法(透明式,Leaf 也需要实现)
    public void add(EmployeeComponent c) {
        throw new UnsupportedOperationException();
    }
    public void remove(EmployeeComponent c) {
        throw new UnsupportedOperationException();
    }
    public List<EmployeeComponent> getSubordinates() {
        return null;
    }
}

// --- 2. 叶子构件 (Leaf) ---
// 普通员工,不能再包含下属
public class BasicEmployee extends EmployeeComponent {
    public BasicEmployee(String name) {
        super(name, "Staff");
    }

    @Override
    public void displayDetails() {
        System.out.println(String.format("  -> Staff: %s", name));
    }
    // 不支持管理方法,保留空实现或抛出异常
}

// --- 3. 树枝构件 (Composite) ---
// 经理/部门主管,可以包含下属
public class Manager extends EmployeeComponent {
    private List<EmployeeComponent> subordinates = new ArrayList<>();

    public Manager(String name, String role) {
        super(name, role);
    }

    @Override
    public void add(EmployeeComponent c) {
        subordinates.add(c);
    }
    @Override
    public void remove(EmployeeComponent c) {
        subordinates.remove(c);
    }
    @Override
    public List<EmployeeComponent> getSubordinates() {
        return subordinates;
    }

    @Override
    public void displayDetails() {
        System.out.println(String.format("== Manager: %s (%s) ==", name, role));
        for (EmployeeComponent sub : subordinates) {
            sub.displayDetails(); // 递归调用
        }
    }
}

// --- 4. 客户端调用 (Client) ---
public class CompositePatternDemo {
    public static void main(String[] args) {
        // 创建组合结构
        Manager ceo = new Manager("John", "CEO");
        
        Manager salesHead = new Manager("Robert", "Head Sales");
        BasicEmployee salesStaff1 = new BasicEmployee("Richard");
        salesHead.add(salesStaff1);

        Manager marketingHead = new Manager("Michel", "Head Marketing");
        BasicEmployee marketingStaff1 = new BasicEmployee("Laura");
        marketingHead.add(marketingStaff1);
        
        ceo.add(salesHead);
        ceo.add(marketingHead);

        // 使用一致的接口处理整个结构
        ceo.displayDetails();
    }
}

3.2. Python 代码示例

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
from abc import ABC, abstractmethod

# --- 1. 抽象构件 (Component) ---
class FileSystemComponent(ABC):
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def display(self, indent=""):
        pass

    # 管理操作(透明式):Leaf会抛出异常或空实现
    def add(self, component):
        raise NotImplementedError("Cannot add to a Leaf.")

    def remove(self, component):
        raise NotImplementedError("Cannot remove from a Leaf.")

# --- 2. 叶子构件 (Leaf) ---
class File(FileSystemComponent):
    def display(self, indent=""):
        print(f"{indent}📄 {self.name}")
    # add/remove 方法继承自 Component 并抛出异常

# --- 3. 树枝构件 (Composite) ---
class Folder(FileSystemComponent):
    def __init__(self, name):
        super().__init__(name)
        self.children = []

    def add(self, component):
        self.children.append(component)

    def remove(self, component):
        self.children.remove(component)

    def display(self, indent=""):
        print(f"{indent}📁 {self.name}")
        for child in self.children:
            child.display(indent + "  ") # 递归调用

# --- 4. 客户端调用 (Client) ---
if __name__ == "__main__":
    
    # 建立文件系统树
    root = Folder("Root")
    
    docs_folder = Folder("Documents")
    docs_folder.add(File("Resume.pdf"))
    docs_folder.add(File("Notes.txt"))
    
    images_folder = Folder("Images")
    images_folder.add(File("Sunset.jpg"))
    images_folder.add(File("Logo.png"))
    
    root.add(docs_folder)
    root.add(images_folder)
    root.add(File("README.md")) # 直接在根目录下添加文件

    # 客户端使用统一的 display 接口处理整个结构
    root.display() 

4. 模式优点与缺点

4.1. 优点

  1. 高层模块调用简单:客户端可以忽略组合对象和单个对象的差异,使用一致的方式处理结构中的所有对象,简化了高层模块的调用。
  2. 节点自由增加:增加新的叶子构件或树枝构件时,无需修改原有代码,符合“开闭原则”。
  3. 清晰的层次结构:有效地表示了自然界或业务中的“部分-整体”层次结构。

4.2. 缺点

  1. 类型检查复杂:在透明式组合中,由于叶子构件实现了它不需要的方法(如 add()),可能会在运行时出现错误(如在叶子上调用 add())。
  2. 违反依赖倒置原则(通常指实现层面):如果抽象构件定义为具体类而不是接口,或者如果 Leaf 和 Composite 的声明过于依赖具体实现,则可能违反该原则。

5. 适用环境

  1. 您想表示对象的部分-整体层次结构(树形结构)
  2. 您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
  3. 需要维护和展示树形菜单、文件和文件夹管理等具有递归结构的场景。
  4. 需要从一个整体中能够独立出部分模块或功能的场景。
本文由作者按照 CC BY 4.0 进行授权