Python 组合模式

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 的动态特性让组合模式实现非常自然。
  • 常与迭代器模式结合使用(遍历树结构)。
  • 在实际项目中非常常见:
  • lxmlxml.etree.ElementTree 的 XML 树
  • PyQt / Tkinter 的控件树
  • 游戏引擎中的场景图(Scene Graph)
  • AST(抽象语法树)在编译器中
  • 注意安全问题:如果叶子节点实现了某些组合节点没有的方法,客户端需小心(可用 NotImplementedError)。

注意事项

  • 组合模式会产生很多小对象(尤其是叶子多时),注意性能。
  • 如果树很深,递归操作可能导致栈溢出(可用迭代方式替代)。
  • 叶子和组合的接口要保持一致(至少在客户端关心的操作上)。

组合模式是处理树形结构最优雅的方式之一,尤其在需要递归处理层次结构时。

如果你想看更高级的例子(如带删除、搜索功能的菜单系统、组织架构权限控制、结合访问者模式的图形编辑器),欢迎继续问!

文章已创建 3511

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部