概念解析
组合模式,指的是:将对象组合成树形结构以表示“整体-部分”的层次结构,组合使得用户对单个对象和复合对象的使用具备一致性。其设计要点为:
- 理清部分与整体的关系,了解对象的组成结构
- 组合模式为一种具有层次关系的树形结构,不能再分的叶子节点为具体的组件,即最小的逻辑单元;具有子节点(由多个子组件组成)被称为复合组件,即组合对象;对于复合组件和子组件两者,用户对两者的使用具备一致性。
组合模式的类图如下:
组合模式的优点在于:
- 调用简单,组合对象可以像一般对象一样使用
- 组合对象可以自由地增加、删除组件,可灵活地组合不同对象
缺点在于:
- 层次结构太深的话,组合结构会变得很复杂
组合模式适用的场景:
- 对象之间具备明显的”部分-整体”的关系,或者层次关系
- 组合对象和单一对象具有相同或类似行为(方法),用户希望统一地使用组合结构中的所有对象
设计模板
子组件和复合组件的代码模板如下:
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("")