rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • 组合模式(Composite Pattern)

  • 一、组合模式核心概念(通用)
    • 1. 定义
    • 2. 意图
    • 3. 通用核心组件
  • 二、组合模式详细解析(以 “文件系统” 为例)
    • 1. 结构
    • 2. 类图(Mermaid)
    • 3. 时序图(Mermaid)
    • 4. 优点
    • 5. 缺点
  • 三、Java 代码实现(文件系统示例)
    • 1. 抽象构件(FileSystemComponent)
    • 2. 叶子构件(File)
    • 3. 容器构件(Folder)
    • 4. 客户端(Client)
    • 5. 输出结果
  • 四、适用环境
  • 五、模式分析
    • 1. 核心本质
    • 2. 与其他模式的区别
  • 六、模式扩展
    • 1. 透明组合模式(Transparent Composite)
    • 2. 安全组合模式(Safe Composite)
    • 3. 带权重的组合模式
    • 4. 带缓存的组合模式
  • 七、模式应用(通用 + Android)
    • 1. 通用领域应用
    • 2. Android 中的应用
      • (1)View 与 ViewGroup(UI 组件树)
      • (2)Menu 与 SubMenu(菜单系统)
      • (3)RecyclerView 的 Item 嵌套(复杂列表)
  • 八、总结

组合模式(Composite Pattern)

组合模式是结构型设计模式的核心之一,其核心目标是 “将对象组合成树形结构,以表示‘部分 - 整体’的层次关系”,并让客户端对单个对象(叶子节点)和组合对象(容器节点)的访问具有一致性。它如同文件系统中的 “文件夹与文件”—— 文件夹(容器)可包含子文件夹(子容器)和文件(叶子),客户端无论是操作单个文件还是整个文件夹,都可通过统一接口(如 “删除”“复制”)完成。

一、组合模式核心概念(通用)

在展开细节前,先明确组合模式的通用定义、意图等核心要素:

1. 定义

将对象组合成树形结构以表示 “部分 - 整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。

2. 意图

  • 统一 “部分” 与 “整体” 的访问接口:客户端无需区分单个对象(叶子)和组合对象(容器),可通过相同接口操作。
  • 简化客户端代码:避免客户端使用大量 if-else 判断 “当前对象是叶子还是容器”,降低代码复杂度。
  • 动态构建树形结构:支持在运行时动态添加 / 删除节点(叶子或容器),灵活扩展层次结构。

3. 通用核心组件

组合模式的核心是通过 “抽象构件” 统一接口,再由 “叶子构件” 和 “容器构件” 实现具体逻辑,三者构成基本结构:

角色名称职责描述
Component(抽象构件)定义所有构件(叶子 / 容器)的统一接口,包含客户端所需的核心方法(如 operation()),同时可能定义管理子构件的方法(如 add()/remove(),默认抛出异常或空实现)。
Leaf(叶子构件)树形结构的 “叶子节点”,表示 “部分”,无子构件。实现 Component 接口的核心方法,但不实现子构件管理方法(或直接抛出 “不支持该操作” 的异常)。
Composite(容器构件)树形结构的 “非叶子节点”,表示 “整体”,可包含子构件(Leaf 或其他 Composite)。实现 Component 接口的核心方法(通常需遍历子构件并调用其 operation()),同时实现子构件管理方法(add()/remove()/getChild())。

二、组合模式详细解析(以 “文件系统” 为例)

以日常使用的 “文件系统” 为场景展开 ——File(文件,叶子)和 Folder(文件夹,容器)都属于 “文件系统构件”,客户端可通过统一接口(如 showInfo() 显示信息、delete() 删除)操作,无需区分是文件还是文件夹。

1. 结构

  1. Component(抽象构件):FileSystemComponent,定义 showInfo()(显示构件信息)、delete()(删除构件),以及子构件管理方法 add()/remove()/getChild()。
  2. Leaf(叶子构件):File,无子女,实现 showInfo()(显示文件名和大小)和 delete()(删除文件),子构件管理方法抛出异常。
  3. Composite(容器构件):Folder,可包含 File 或其他 Folder,实现 showInfo()(显示文件夹名 + 子构件数量)和 delete()(递归删除所有子构件 + 自身),同时实现子构件管理方法。

2. 类图(Mermaid)

classDiagram
    class FileSystemComponent {
        <<Abstract>>
        +showInfo(): void  // 核心方法:显示构件信息
        +delete(): void    // 核心方法:删除构件
        +add(component: FileSystemComponent): void  // 管理子构件:添加
        +remove(component: FileSystemComponent): void  // 管理子构件:删除
        +getChild(index: int): FileSystemComponent  // 管理子构件:获取子构件
    }

    class File {
        -name: String
        -size: long  // 文件大小(字节)
        +File(name: String, size: long)
        +showInfo(): void
        +delete(): void
        +add(component: FileSystemComponent): void  // 抛出UnsupportedOperationException
        +remove(component: FileSystemComponent): void  // 抛出UnsupportedOperationException
        +getChild(index: int): FileSystemComponent  // 抛出UnsupportedOperationException
    }

    class Folder {
        -name: String
        -children: List~FileSystemComponent~  // 存储子构件(File/Folder)
        +Folder(name: String)
        +showInfo(): void
        +delete(): void
        +add(component: FileSystemComponent): void
        +remove(component: FileSystemComponent): void
        +getChild(index: int): FileSystemComponent
    }

		%% 叶子构件继承抽象构件
    FileSystemComponent <|-- File
    
    %% 容器构件继承抽象构件
    FileSystemComponent <|-- Folder
    
    %% 容器可包含0~N个抽象构件(组合关系)
    Folder "1" *-- "0..*" FileSystemComponent

3. 时序图(Mermaid)

以 “客户端删除包含文件的文件夹” 为例,展示组合模式的调用流程:

sequenceDiagram
    participant Client(客户端)
    participant Folder(容器构件:我的文档)
    participant File(叶子构件:简历.pdf)
    participant SubFolder(容器构件:照片)
    participant SubFile(叶子构件:旅行.jpg)

    %% 1. 客户端调用文件夹的delete()方法
    Client->>Folder: delete()(删除“我的文档”)
    %% 2. 文件夹先删除所有子构件
    Folder->>File: delete()(删除“简历.pdf”)
    File-->>Folder: 确认删除(返回成功)
    Folder->>SubFolder: delete()(删除“照片”子文件夹)
    %% 3. 子文件夹删除自身的子构件
    SubFolder->>SubFile: delete()(删除“旅行.jpg”)
    SubFile-->>SubFolder: 确认删除(返回成功)
    %% 4. 子文件夹删除自身
    SubFolder-->>Folder: 确认删除(返回成功)
    %% 5. 根文件夹删除自身
    Folder-->>Client: 确认删除(返回成功)

4. 优点

  • 客户端一致性访问:无需区分叶子和容器,通过统一接口操作,简化客户端代码(如删除 “文件” 和 “文件夹” 都调用 delete())。
  • 灵活扩展树形结构:可在运行时动态添加 / 删除节点(如给文件夹新增子文件、删除子文件夹),符合 “开闭原则”。
  • 清晰表示层次关系:通过树形结构直观体现 “部分 - 整体” 关系(如文件夹包含子文件夹和文件),逻辑清晰。
  • 简化递归操作:容器构件的方法(如 delete())可通过递归调用子构件的方法,自动处理所有子节点,无需客户端干预。

5. 缺点

  • 限制子构件类型困难:若需限制容器只能包含特定类型的子构件(如 “照片文件夹只能包含图片文件”),需在 add() 中增加判断逻辑,破坏了 “开闭原则”(新增类型需修改容器代码)。
  • 抽象构件设计复杂:抽象构件需同时定义 “核心业务方法” 和 “子构件管理方法”,叶子构件需覆盖子构件管理方法并抛出异常,增加了抽象层的设计复杂度。
  • 性能开销(递归场景):若树形结构过深(如嵌套 100 层文件夹),容器构件的递归操作(如 delete())可能导致栈溢出或性能下降。

三、Java 代码实现(文件系统示例)

1. 抽象构件(FileSystemComponent)

定义所有构件的统一接口,子构件管理方法默认抛出异常(叶子构件可直接继承,容器构件需重写):

import java.util.List;

// 抽象构件:文件系统构件
public abstract class FileSystemComponent {
    // 核心方法:显示构件信息(文件名/文件夹名+详情)
    public abstract void showInfo();

    // 核心方法:删除构件
    public abstract void delete();

    // 子构件管理:添加(默认不支持,抛出异常)
    public void add(FileSystemComponent component) {
        throw new UnsupportedOperationException("当前构件不支持添加操作");
    }

    // 子构件管理:删除(默认不支持,抛出异常)
    public void remove(FileSystemComponent component) {
        throw new UnsupportedOperationException("当前构件不支持删除操作");
    }

    // 子构件管理:获取子构件(默认不支持,抛出异常)
    public FileSystemComponent getChild(int index) {
        throw new UnsupportedOperationException("当前构件不支持获取子构件操作");
    }
}

2. 叶子构件(File)

无子女,实现核心方法,子构件管理方法直接继承抽象类的 “抛出异常” 逻辑:

// 叶子构件:文件(无子女)
public class File extends FileSystemComponent {
    private final String name;  // 文件名
    private final long size;    // 文件大小(字节)

    public File(String name, long size) {
        this.name = name;
        this.size = size;
    }

    // 显示文件信息:文件名+大小
    @Override
    public void showInfo() {
        System.out.printf("文件:%s,大小:%d KB(%d 字节)%n", 
                name, size / 1024, size);
    }

    // 删除文件:模拟删除逻辑
    @Override
    public void delete() {
        System.out.printf("已删除文件:%s%n", name);
    }
}

3. 容器构件(Folder)

可包含子构件,重写所有方法,子构件管理通过 List 实现,核心方法通过递归调用子构件逻辑:

import java.util.ArrayList;
import java.util.List;

// 容器构件:文件夹(可包含File或Folder)
public class Folder extends FileSystemComponent {
    private final String name;
    private final List<FileSystemComponent> children;  // 存储子构件

    public Folder(String name) {
        this.name = name;
        this.children = new ArrayList<>();
    }

    // 显示文件夹信息:文件夹名+子构件数量
    @Override
    public void showInfo() {
        System.out.printf("文件夹:%s,包含 %d 个构件%n", 
                name, children.size());
        // 递归显示所有子构件信息
        for (FileSystemComponent component : children) {
            component.showInfo();
        }
    }

    // 删除文件夹:先递归删除所有子构件,再删除自身
    @Override
    public void delete() {
        // 1. 递归删除子构件
        for (FileSystemComponent component : children) {
            component.delete();
        }
        // 2. 删除自身
        System.out.printf("已删除文件夹:%s%n", name);
    }

    // 新增子构件(File或Folder)
    @Override
    public void add(FileSystemComponent component) {
        children.add(component);
        System.out.printf("已向文件夹【%s】添加:%s%n", 
                name, component.getClass().getSimpleName());
    }

    // 移除子构件
    @Override
    public void remove(FileSystemComponent component) {
        if (children.remove(component)) {
            System.out.printf("已从文件夹【%s】移除:%s%n", 
                    name, component.getClass().getSimpleName());
        } else {
            System.out.printf("文件夹【%s】中不存在该构件%n", name);
        }
    }

    // 获取指定索引的子构件
    @Override
    public FileSystemComponent getChild(int index) {
        if (index >= 0 && index < children.size()) {
            return children.get(index);
        }
        throw new IndexOutOfBoundsException("子构件索引越界");
    }
}

4. 客户端(Client)

通过统一接口操作叶子和容器,无需区分类型:

// 客户端:操作文件系统构件
public class Client {
    public static void main(String[] args) {
        // 1. 创建叶子构件(文件)
        FileSystemComponent resumeFile = new File("简历.pdf", 102400);  // 100KB
        FileSystemComponent travelPhoto = new File("旅行.jpg", 204800); // 200KB
        FileSystemComponent notesFile = new File("笔记.txt", 2048);     // 2KB

        // 2. 创建容器构件(文件夹)
        FileSystemComponent photoFolder = new Folder("照片");
        FileSystemComponent docFolder = new Folder("我的文档");

        // 3. 构建树形结构:我的文档 -> 照片文件夹 + 简历文件;照片文件夹 -> 旅行照片
        photoFolder.add(travelPhoto);       // 照片文件夹添加旅行照片
        docFolder.add(resumeFile);          // 我的文档添加简历
        docFolder.add(photoFolder);         // 我的文档添加照片文件夹
        docFolder.add(notesFile);           // 我的文档添加笔记

        // 4. 统一接口操作:显示“我的文档”的所有信息(递归显示子构件)
        System.out.println("=== 显示我的文档结构 ===");
        docFolder.showInfo();

        // 5. 统一接口操作:删除“我的文档”(递归删除所有子构件+自身)
        System.out.println("\n=== 删除我的文档 ===");
        docFolder.delete();
    }
}

5. 输出结果

已向文件夹【照片】添加:File
已向文件夹【我的文档】添加:File
已向文件夹【我的文档】添加:Folder
已向文件夹【我的文档】添加:File
=== 显示我的文档结构 ===
文件夹:我的文档,包含 3 个构件
文件:简历.pdf,大小:100 KB(102400 字节)
文件夹:照片,包含 1 个构件
文件:旅行.jpg,大小:200 KB(204800 字节)
文件:笔记.txt,大小:2 KB(2048 字节)

=== 删除我的文档 ===
已删除文件:简历.pdf
已删除文件:旅行.jpg
已删除文件夹:照片
已删除文件:笔记.txt
已删除文件夹:我的文档

四、适用环境

组合模式适用于以下场景,核心判断标准是 “存在‘部分 - 整体’层次关系,且需统一访问接口”:

  1. 需表示 “部分 - 整体” 层次结构:如文件系统(文件夹 - 文件)、组织架构(公司 - 部门 - 员工)、UI 组件树(布局 - 按钮 - 文本框)。
  2. 客户端需统一访问叶子和容器:客户端无需区分 “单个对象” 和 “组合对象”,希望通过相同接口操作(如删除文件和删除文件夹)。
  3. 需动态扩展层次结构:需在运行时灵活添加 / 删除节点(如给文件夹新增子文件、给部门新增员工),且不影响客户端代码。
  4. 需递归处理树形结构:如 “统计文件夹总大小”“遍历所有 UI 组件” 等场景,容器构件可通过递归自动处理所有子节点。

五、模式分析

1. 核心本质

组合模式的本质是 “统一接口 + 树形结构 + 递归处理”:

  • 统一接口:通过抽象构件消除 “叶子” 和 “容器” 的差异,客户端无需感知类型。
  • 树形结构:通过容器构件的 “子构件列表” 构建层次关系,直观体现 “部分 - 整体”。
  • 递归处理:容器构件的方法(如 delete())通过递归调用子构件的方法,自动覆盖所有子节点,简化逻辑。

2. 与其他模式的区别

  • 与适配器模式的区别:适配器模式解决 “接口不兼容”,不改变对象结构;组合模式解决 “部分 - 整体层次访问”,核心是构建树形结构。
  • 与装饰器模式的区别:装饰器模式不改变对象结构,仅为对象动态添加功能;组合模式改变对象结构(构建树形),核心是统一 “部分” 和 “整体” 的访问。
  • 与迭代器模式的区别:迭代器模式用于遍历集合(包括组合模式的子构件列表);组合模式是构建集合结构的模式,迭代器是遍历结构的工具。

六、模式扩展

组合模式可根据需求扩展出不同变体,核心是调整 “抽象构件” 和 “容器构件” 的设计:

1. 透明组合模式(Transparent Composite)

  • 特点:抽象构件 Component 定义所有子构件管理方法(add()/remove()),叶子构件需覆盖这些方法并抛出异常。
  • 优点:客户端访问更透明(无需区分叶子和容器,所有构件都有相同方法)。
  • 缺点:叶子构件的子构件管理方法无实际意义,可能导致客户端误调用(如给文件调用 add())。
  • 适用场景:客户端完全无需区分叶子和容器,且能保证不调用叶子的子构件管理方法。

2. 安全组合模式(Safe Composite)

  • 特点:抽象构件 Component 仅定义核心业务方法(operation()),子构件管理方法(add()/remove())仅在容器构件 Composite 中定义。
  • 优点:避免客户端误调用叶子的子构件管理方法,安全性更高。
  • 缺点:客户端需区分叶子和容器(调用 add() 前需判断是否为 Composite),破坏了访问的一致性。
  • 适用场景:需严格限制子构件管理操作,避免客户端误操作的场景。

3. 带权重的组合模式

  • 特点:为每个构件添加 “权重” 属性(如文件大小、员工薪资),容器构件的权重为所有子构件权重之和(通过递归计算)。
  • 应用场景:需统计 “整体” 的聚合属性(如文件夹总大小、部门总薪资)。

4. 带缓存的组合模式

  • 特点:容器构件缓存子构件的聚合结果(如总大小),避免每次调用都递归计算,提升性能。
  • 适用场景:树形结构庞大且聚合结果不频繁变化(如统计大型文件夹的总大小)。

七、模式应用(通用 + Android)

1. 通用领域应用

  • 文件系统:Windows/macOS 的文件系统(Folder/File),通过组合模式实现 “文件夹包含文件 / 子文件夹”,统一操作接口(删除、复制)。
  • 组织架构系统:公司管理系统(Company -> Department -> Employee),Employee 是叶子,Department 是容器,统一计算 “部门总人数”“公司总薪资”。
  • 图形绘制系统:如 Photoshop 的图层系统(GroupLayer -> Layer/SubGroup),GroupLayer 是容器,Layer 是叶子,统一执行 “显示图层”“隐藏图层” 操作。
  • XML/JSON 解析:解析后的 DOM 树(Node -> Element/Text),Element 是容器(可包含子 Node),Text 是叶子,统一遍历和修改节点。

2. Android 中的应用

Android 中大量场景依赖 “UI 组件树” 和 “层次结构”,组合模式是核心设计思路:

(1)View 与 ViewGroup(UI 组件树)

  • 角色对应:
    • View:抽象构件(定义 draw()/onTouchEvent() 等核心方法,子构件管理方法默认不支持);
    • 具体 View(如 TextView/Button):叶子构件(无子女,实现 draw() 显示自身);
    • ViewGroup:容器构件(继承 View,实现 addView()/removeView() 管理子 View,draw() 递归绘制所有子 View)。
  • 核心逻辑:客户端(如 Activity)操作 View 或 ViewGroup 时,无需区分类型(如调用 setVisibility() 隐藏按钮或隐藏整个布局),通过统一接口 View 实现。
// 示例:操作 View(叶子)和 ViewGroup(容器)
TextView textView = new TextView(this); // 叶子构件
LinearLayout linearLayout = new LinearLayout(this); // 容器构件

// 统一接口操作:设置可见性(无需区分类型)
textView.setVisibility(View.GONE);
linearLayout.setVisibility(View.GONE);

// 容器构件管理子 View
linearLayout.addView(textView); // 添加叶子构件
linearLayout.removeView(textView); // 移除叶子构件

(2)Menu 与 SubMenu(菜单系统)

  • 角色对应:
    • Menu:抽象构件(定义 add()/removeItem() 等方法);
    • MenuItem:叶子构件(单个菜单项,无子女);
    • SubMenu:容器构件(继承 Menu,可包含 MenuItem 或子 SubMenu,实现多级菜单)。
  • 核心逻辑:客户端创建菜单时,无需区分 “菜单项” 和 “子菜单”,通过 Menu 接口统一添加(如 menu.addSubMenu("设置") 添加子菜单,menu.add("退出") 添加菜单项)。

(3)RecyclerView 的 Item 嵌套(复杂列表)

  • 场景:
    • RecyclerView 的 Item 包含 “子列表”(如 “订单 Item” 包含 “商品子 Item”),此时:
    • 外层 RecyclerView.Adapter 管理 “订单 Item”(容器构件);
    • 内层 RecyclerView.Adapter 管理 “商品子 Item”(叶子构件);
    • 客户端通过统一的 Adapter.notifyDataSetChanged() 刷新整个列表(无论外层还是内层)。

八、总结

组合模式是处理 “部分 - 整体” 层次关系的核心模式,其核心价值在于 “统一接口 + 灵活扩展”:

  1. 核心优势:客户端无需区分叶子和容器,通过统一接口操作,简化代码;树形结构支持动态扩展,符合 “开闭原则”;递归处理自动覆盖所有子节点,减少重复逻辑。
  2. 适用场景:需表示层次结构(如文件系统、UI 组件树)、需统一访问接口(如操作菜单 / 子菜单)的场景。
  3. 实践建议:
    • 优先使用 “透明组合模式”(抽象构件定义所有方法),除非有严格的安全需求(如避免客户端误操作);
    • 若树形结构过深,需注意递归性能(如添加缓存或限制层级);
    • 结合迭代器模式遍历子构件(如 Android 的 ViewGroup 遍历子 View 时使用 getChildAt()),提升代码灵活性。

组合模式不仅是一种设计模式,更是一种 “层次化思维”—— 通过树形结构将复杂的 “整体” 拆解为可管理的 “部分”,同时通过统一接口让客户端无需关注内部结构,是构建复杂系统的重要工具。

最近更新:: 2025/10/22 15:36
Contributors: 罗凯文, luokaiwen