Python 组合模式

组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表示"部分-整体"的层次结构。组合模式让客户端可以统一地处理单个对象和对象组合,无需关心处理的是单个对象还是整个组合结构。

生活中的类比

想象一下文件系统的组织结构:

  • 单个文件:是最基本的单位
  • 文件夹:可以包含多个文件或其他文件夹
  • 无论你操作的是文件还是文件夹,都可以进行"打开"、"删除"、"重命名"等操作

这种"部分-整体"的层次关系就是组合模式的典型应用场景。


组合模式的核心组件

1. 组件接口(Component)

这是组合模式的核心,定义了所有对象(包括叶子节点和组合节点)的通用接口。

实例

from abc import ABC, abstractmethod
from typing import List

class FileSystemComponent(ABC):
    """文件系统组件抽象基类"""
   
    def __init__(self, name: str):
        self.name = name
        self.parent = None
   
    @abstractmethod
    def display(self, indent: int = 0) -> None:
        """显示组件信息"""
        pass
   
    @abstractmethod
    def get_size(self) -> int:
        """获取组件大小"""
        pass
   
    def get_path(self) -> str:
        """获取完整路径"""
        if self.parent:
            return f"{self.parent.get_path()}/{self.name}"
        return self.name

2. 叶子节点(Leaf)

表示组合中的叶子对象,叶子节点没有子节点。

实例

class File(FileSystemComponent):
    """文件类 - 叶子节点"""
   
    def __init__(self, name: str, size: int):
        super().__init__(name)
        self._size = size
   
    def display(self, indent: int = 0) -> None:
        """显示文件信息"""
        spaces = "  " * indent
        print(f"{spaces}📄 {self.name} ({self._size} bytes)")
   
    def get_size(self) -> int:
        """返回文件大小"""
        return self._size

3. 组合节点(Composite)

表示可以包含子组件的组合对象,定义了存储子组件的方法。

实例

class Directory(FileSystemComponent):
    """目录类 - 组合节点"""
   
    def __init__(self, name: str):
        super().__init__(name)
        self._children: List[FileSystemComponent] = []
   
    def add(self, component: FileSystemComponent) -> None:
        """添加子组件"""
        component.parent = self
        self._children.append(component)
   
    def remove(self, component: FileSystemComponent) -> None:
        """移除子组件"""
        self._children.remove(component)
        component.parent = None
   
    def display(self, indent: int = 0) -> None:
        """显示目录及其所有子组件"""
        spaces = "  " * indent
        print(f"{spaces}📁 {self.name}/")
       
        # 递归显示所有子组件
        for child in self._children:
            child.display(indent + 1)
   
    def get_size(self) -> int:
        """计算目录总大小(包含所有子组件)"""
        total_size = 0
        for child in self._children:
            total_size += child.get_size()
        return total_size
   
    def find_component(self, name: str) -> FileSystemComponent:
        """查找指定名称的组件"""
        for child in self._children:
            if child.name == name:
                return child
            if isinstance(child, Directory):
                found = child.find_component(name)
                if found:
                    return found
        return None

完整示例:文件系统模拟

让我们通过一个完整的例子来演示组合模式的实际应用:

实例

def demonstrate_composite_pattern():
    """演示组合模式的使用"""
   
    # 创建根目录
    root = Directory("root")
   
    # 创建子目录
    documents = Directory("documents")
    pictures = Directory("pictures")
    music = Directory("music")
   
    # 创建文件
    readme = File("README.txt", 1024)
    notes = File("notes.md", 2048)
    photo1 = File("vacation.jpg", 1536000)
    photo2 = File("family.png", 2048000)
    song1 = File("song1.mp3", 4096000)
    song2 = File("song2.mp3", 5120000)
   
    # 构建目录结构
    root.add(readme)
    root.add(documents)
    root.add(pictures)
    root.add(music)
   
    documents.add(notes)
   
    pictures.add(photo1)
    pictures.add(photo2)
   
    music.add(song1)
    music.add(song2)
   
    # 显示整个文件系统结构
    print("=== 文件系统结构 ===")
    root.display()
   
    print("\n=== 大小统计 ===")
    print(f"根目录总大小: {root.get_size()} bytes")
    print(f"图片目录大小: {pictures.get_size()} bytes")
    print(f"音乐目录大小: {music.get_size()} bytes")
   
    print("\n=== 路径信息 ===")
    print(f"文件路径: {photo1.get_path()}")
    print(f"目录路径: {pictures.get_path()}")
   
    print("\n=== 查找组件 ===")
    found = root.find_component("song1.mp3")
    if found:
        print(f"找到文件: {found.get_path()}")

# 运行演示
if __name__ == "__main__":
    demonstrate_composite_pattern()

运行上述代码,你将看到以下输出:


组合模式的 UML 类图


组合模式的优缺点

优点

  1. 统一的接口:客户端可以一致地使用单个对象和组合对象
  2. 开闭原则:容易添加新类型的组件,无需修改现有代码
  3. 简化客户端代码:客户端不需要判断处理的是单个对象还是组合
  4. 灵活的层次结构:可以构建复杂的树形结构

缺点

  1. 设计复杂性:过度通用化的设计可能使系统变得复杂
  2. 类型安全问题:在某些情况下,可能需要运行时类型检查
  3. 性能考虑:对于大型层次结构,递归操作可能影响性能

实际应用场景

1. GUI 组件系统

实例

class UIComponent:
    """UI 组件基类"""
    def render(self):
        pass
    def add(self, component):
        pass

class Button(UIComponent):
    """按钮 - 叶子节点"""
    def render(self):
        print("渲染按钮")

class Panel(UIComponent):
    """面板 - 组合节点"""
    def __init__(self):
        self.children = []
   
    def add(self, component):
        self.children.append(component)
   
    def render(self):
        print("开始渲染面板")
        for child in self.children:
            child.render()
        print("结束渲染面板")

2. 组织架构管理

实例

class Employee:
    """员工基类"""
    def get_salary(self):
        pass

class Developer(Employee):
    """开发人员 - 叶子节点"""
    def __init__(self, salary):
        self.salary = salary
   
    def get_salary(self):
        return self.salary

class Department(Employee):
    """部门 - 组合节点"""
    def __init__(self):
        self.employees = []
   
    def add(self, employee):
        self.employees.append(employee)
   
    def get_salary(self):
        return sum(emp.get_salary() for emp in self.employees)

实践练习

练习 1:扩展文件系统

为文件系统添加以下功能:

  1. 实现文件复制功能
  2. 添加文件类型过滤搜索
  3. 实现目录深度限制显示

练习 2:菜单系统设计

设计一个餐厅菜单系统,要求:

  • 菜单可以包含菜品或子菜单
  • 能够计算总价格
  • 支持按类别筛选菜品

练习 3:组织结构图

创建一个公司组织结构系统:

  • 员工可以是个人或部门
  • 部门可以包含其他员工或子部门
  • 能够计算部门总人数和总薪资

总结

组合模式通过统一的接口来处理单个对象和对象组合,使得客户端代码更加简洁和灵活。这种模式特别适用于具有层次结构的场景,如文件系统、GUI 组件、组织架构等。

关键要点

  • 组件接口定义了所有对象的通用操作
  • 叶子节点实现基础功能
  • 组合节点管理子组件并提供聚合操作
  • 客户端无需区分单个对象和对象组合