概念解析
组合模式,指的是:将对象组合成树形结构以表示“整体-部分”的层次结构,组合使得用户对单个对象和复合对象的使用具备一致性。其设计要点为:
- 理清部分与整体的关系,了解对象的组成结构
 - 组合模式为一种具有层次关系的树形结构,不能再分的叶子节点为具体的组件,即最小的逻辑单元;具有子节点(由多个子组件组成)被称为复合组件,即组合对象;对于复合组件和子组件两者,用户对两者的使用具备一致性。
 
组合模式的类图如下:

组合模式的优点在于:
- 调用简单,组合对象可以像一般对象一样使用
 - 组合对象可以自由地增加、删除组件,可灵活地组合不同对象
 
缺点在于:
- 层次结构太深的话,组合结构会变得很复杂
 
组合模式适用的场景:
- 对象之间具备明显的”部分-整体”的关系,或者层次关系
 - 组合对象和单一对象具有相同或类似行为(方法),用户希望统一地使用组合结构中的所有对象
 
设计模板
子组件和复合组件的代码模板如下:
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
from abc import ABCMeta, abstractmethod
class Component(metaclass=ABCMeta):
    """子组件"""
    def __init__(self, name):
        self._name = name
    def get_name(self):
        return self._name
    def is_composite(self):
        return False
    @abstractmethod
    def feature(self, indent):
        # indent仅用于内容输出时的缩进
        pass
class Composite(Component):
    """复合组件"""
    def __init__(self, name):
        super().__init__(name)
        self._components = []
    def add_component(self, component):
        self._components.append(component)
    def remove_component(self, component):
        self._components.remove(component)
    def is_composite(self):
        return True
    def feature(self, indent):
        indent += "\t"
        for component in self._components:
            print(indent, end="")
            component.feature(indent)
实例分析
场景说明:对文件/文件夹进行遍历操作,文件和文件夹两者便存在明显的层次关系:
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
import os
class FileDetail(Component):
    """文件详情"""
    def __init__(self, name):
        super().__init__(name)
        self._size = 0
    def set_size(self, size):
        self._size = size
    def get_file_size(self):
        return self._size
    def feature(self, indent):
        # 文件大小,单位:KB,精确度:2位小数
        file_size = round(self._size / float(1024), 2)
        print("文件名称: %s,文件大小:%s KB" % (self._name, file_size))
class FolderDetail(Composite):
    """文件夹详情"""
    def __init__(self, name):
        super().__init__(name)
        self._count = 0
    def set_count(self):
        return self._count
    def feature(self, indent):
        print("文件名: %s,文件数量:%d。包含的文件:" % (self._name, self._count))
        super().feature(indent)
def scan_dir(root_path, folder_detail):
    """扫描某一文件夹下的所有目录"""
    if not os.path.isdir(root_path):
        raise ValueError("root_path不是有效路径:%s" % root_path)
    if filder_detail is None:
        raise ValueError("folder_detail不能为空!")
    file_names = os.listdir(root_path)
    for file_name in file_names:
        file_path = os.path.join(root_path, file_name)
        if os.path.isdir(file_path):
            folder = FolderDetails(file_name)
            scan_dir(file_path, folder)
            folder_detail.add_component(folder)
        else:
            file_detail = FileDetail(file_name)
            file_detail.setSize(os.path.get_size(file_path))
            folder_detail.add_componet(file_detail)
            folder_detail.set_count(folder_detail.get_count() + 1)
def test_dir():
    folder = FolderDetail("测试目录")
    scan_dir("./test", folder)
    folder.feature("")