Python 中的组合模式(Composite Pattern)
组合模式是一种结构型设计模式,其核心目的是:
将对象组合成树形结构以表示“部分-整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
形象比喻:就像文件系统——文件夹(容器)可以包含文件(叶子)和子文件夹,而文件夹又可以嵌套文件夹。客户端可以统一对待文件和文件夹(例如统一遍历、计算大小)。
组合模式的优点
- 定义了包含基本对象和组合对象的类层次结构
- 简化客户端代码:客户端可以一致处理单个对象和组合对象
- 更容易添加新类型的组件
- 符合“开闭原则”:对扩展开放(新增组件类型)
典型应用场景
- 文件系统(文件 + 目录)
- GUI 组件树(窗口、面板、按钮、标签等)
- 组织架构(公司、部门、员工)
- 图形绘制(基本图形 + 图形组)
- XML / HTML DOM 树
- 菜单系统(菜单项 + 子菜单)
Python 实现示例:文件系统
from abc import ABC, abstractmethod
from typing import List
# 组件接口(Component)
class FileSystemComponent(ABC):
@abstractmethod
def get_name(self) -> str:
pass
@abstractmethod
def get_size(self) -> int:
pass
@abstractmethod
def display(self, indent: str = ""):
pass
# 叶子节点(Leaf):文件
class File(FileSystemComponent):
def __init__(self, name: str, size: int):
self.name = name
self.size = size
def get_name(self) -> str:
return self.name
def get_size(self) -> int:
return self.size
def display(self, indent: str = ""):
print(f"{indent}📄 {self.name} ({self.size} KB)")
# 组合节点(Composite):目录
class Directory(FileSystemComponent):
def __init__(self, name: str):
self.name = name
self.children: List[FileSystemComponent] = []
def get_name(self) -> str:
return self.name
def add(self, component: FileSystemComponent):
self.children.append(component)
def remove(self, component: FileSystemComponent):
self.children.remove(component)
def get_size(self) -> int:
# 递归计算总大小
return sum(child.get_size() for child in self.children)
def display(self, indent: str = ""):
print(f"{indent}📁 {self.name} ({self.get_size()} KB)")
for child in self.children:
child.display(indent + " ")
# 客户端使用(统一对待文件和目录)
if __name__ == "__main__":
# 创建文件
file1 = File("document.pdf", 1024)
file2 = File("photo.jpg", 2048)
file3 = File("script.py", 256)
# 创建目录
src_dir = Directory("src")
src_dir.add(File("main.py", 512))
src_dir.add(File("utils.py", 128))
docs_dir = Directory("docs")
docs_dir.add(file1)
docs_dir.add(file2)
# 根目录
root = Directory("MyProject")
root.add(src_dir)
root.add(docs_dir)
root.add(file3)
# 统一显示整个结构
root.display()
print("\n总大小:", root.get_size(), "KB")
输出:
📁 MyProject (6016 KB)
📁 src (640 KB)
📄 main.py (512 KB)
📄 utils.py (128 KB)
📁 docs (3072 KB)
📄 document.pdf (1024 KB)
📄 photo.jpg (2048 KB)
📄 script.py (256 KB)
总大小: 6016 KB
客户端代码完全不需要区分是文件还是目录,都调用相同的 display() 和 get_size() 方法。
更实用的例子:GUI 组件树
class Graphic(ABC):
@abstractmethod
def draw(self):
pass
class Dot(Graphic):
def __init__(self, x, y):
self.x, self.y = x, y
def draw(self):
print(f"绘制点 ({self.x}, {self.y})")
class Circle(Graphic):
def __init__(self, x, y, radius):
self.x, self.y, self.radius = x, y, radius
def draw(self):
print(f"绘制圆 中心({self.x},{self.y}) 半径 {self.radius}")
class CompoundGraphic(Graphic):
def __init__(self):
self.children: List[Graphic] = []
def add(self, child: Graphic):
self.children.append(child)
def remove(self, child: Graphic):
self.children.remove(child)
def draw(self):
print("绘制复合图形 {")
for child in self.children:
child.draw()
print("}")
# 使用
compound = CompoundGraphic()
compound.add(Dot(10, 20))
compound.add(Circle(50, 50, 30))
big_compound = CompoundGraphic()
big_compound.add(compound)
big_compound.add(Dot(100, 100))
big_compound.draw()
组合模式结构总结
| 角色 | 说明 |
|---|---|
| Component | 抽象组件(FileSystemComponent / Graphic) |
| Leaf | 叶子节点(File / Dot / Circle) |
| Composite | 组合节点(Directory / CompoundGraphic) |
| Client | 统一操作 Component 的客户端 |
组合模式 vs 其他模式对比
| 模式 | 目的 | 结构特点 |
|---|---|---|
| 组合 | 部分-整体层次结构,一致处理 | 树形结构 |
| 装饰器 | 动态添加职责 | 链式包装 |
| 享元 | 共享大量细粒度对象 | 共享叶子节点 |
| 访问者 | 在组合结构上添加新操作 | 双分派 |
Python 中的实用建议
- Python 的动态特性让组合模式实现非常自然。
- 常与迭代器模式结合使用(遍历树结构)。
- 在实际项目中非常常见:
lxml或xml.etree.ElementTree的 XML 树PyQt/Tkinter的控件树- 游戏引擎中的场景图(Scene Graph)
- AST(抽象语法树)在编译器中
- 注意安全问题:如果叶子节点实现了某些组合节点没有的方法,客户端需小心(可用
NotImplementedError)。
注意事项
- 组合模式会产生很多小对象(尤其是叶子多时),注意性能。
- 如果树很深,递归操作可能导致栈溢出(可用迭代方式替代)。
- 叶子和组合的接口要保持一致(至少在客户端关心的操作上)。
组合模式是处理树形结构最优雅的方式之一,尤其在需要递归处理层次结构时。
如果你想看更高级的例子(如带删除、搜索功能的菜单系统、组织架构权限控制、结合访问者模式的图形编辑器),欢迎继续问!