设计模式学习(11) 23-9 组合模式

文章目录

  • 0.个人感悟
  • [1. 概念](#1. 概念)
  • [2. 适配场景](#2. 适配场景)
    • [2.1 适合的场景](#2.1 适合的场景)
    • [2.2 常见场景举例](#2.2 常见场景举例)
  • [3. 实现方法](#3. 实现方法)
    • [3.1 实现思路](#3.1 实现思路)
    • [3.2 UML类图](#3.2 UML类图)
    • [3.3 代码示例](#3.3 代码示例)
  • [4. 优缺点](#4. 优缺点)
    • [4.1 优点](#4.1 优点)
    • [4.2 缺点](#4.2 缺点)
  • [5. 源码分析(JDK中的组合模式实现)](#5. 源码分析(JDK中的组合模式实现))

0.个人感悟

  • 组合模式的应用场景比较专,适合树状嵌套场景,统一去处理单个对象和组合对象
  • 可以扩展学习学习数据结构中的tree
  • 使用的时候注意简单对象和组合对象的一致性,如果差异大,不建议使用

1. 概念

英文定义 (《设计模式:可复用面向对象软件的基础》)

Compose objects into tree structures to represent part-whole hierarchies. Composite

lets client treat individual objects and compositions of objects uniformly.

中文翻译

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

理解

  • 组合模式的核心思想是用一致的方式处理单个对象和组合对象
  • 通过树形结构组织对象,形成"部分-整体"的层次关系
  • 客户端无需关心当前处理的是单个对象还是组合对象,简化了客户端代码

2. 适配场景

2.1 适合的场景

  1. 需要表示对象的"部分-整体"层次结构(如文件系统、组织架构)
  2. 希望客户端忽略组合对象与单个对象的差异,统一地使用结构中的所有对象
  3. 需要对树形结构中的所有节点执行统一操作(如统计、渲染、搜索)
  4. 系统需要动态地添加或删除组件,且组件可能嵌套多层

2.2 常见场景举例

  • GUI界面开发:窗口包含面板,面板包含按钮、文本框等控件
  • 文件系统:文件夹可以包含文件或子文件夹
  • 组织架构:部门包含员工和子部门
  • 菜单系统:菜单包含菜单项或子菜单
  • 图形编辑器:复杂图形由简单图形组合而成

3. 实现方法

3.1 实现思路

  1. 定义抽象组件(Component)接口或抽象类:声明所有对象的共同接口,包括管理子组件的方法(添加、删除、获取子组件)
  2. 创建叶子(Leaf)类:实现Component接口,表示树中的叶子节点(没有子节点)
  3. 创建组合(Composite)类:实现Component接口,包含子组件集合,并实现子组件的管理方法
  4. 客户端通过Component接口与所有对象交互,无需区分是单个对象还是组合对象

3.2 UML类图

角色说明

  • Component(抽象组件):定义组合对象和叶子对象的共同接口
  • Leaf(叶子):没有子组件的简单对象
  • Composite(组合):包含子组件的复杂对象,存储和管理子组件

3.3 代码示例

背景:电脑文件系统,支持展开梳妆结构,其中叶子结点是文件,复杂对象是文件夹

定义统一的文件系统组件:

java 复制代码
public abstract class FileSystemComponent {  
    protected String name;  
  
    public FileSystemComponent(String name) {  
        this.name = name;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
    }  
  
    /**  
     * @description display  
     * @author bigHao  
     * @date 2026/1/11  
     **/    
     public abstract void display();  
  
    /**  
     * @param component 组件  
     * @description 添加操作 文件不支持  
     * @author bigHao  
     * @date 2026/1/11  
     **/    
     public void add(FileSystemComponent component) {  
        throw new UnsupportedOperationException("不支持添加操作");  
    }  
  
    /**  
     * @param component 组件  
     * @description 移除操作,文件不支持  
     * @author bigHao  
     * @date 2026/1/11  
     **/    
     public void remove(FileSystemComponent component) {  
        throw new UnsupportedOperationException("不支持删除操作");  
    }  
  
    /**  
     * @param index 索引  
     * @return FileSystemComponent 子对象  
     * @description 获取子对象 文件不支持  
     * @author bigHao  
     * @date 2026/1/11  
     **/    
     public FileSystemComponent getChild(int index) {  
        throw new UnsupportedOperationException("不支持获取子组件操作");  
    }  
}

叶子节点:

java 复制代码
public class File extends FileSystemComponent {  
    private String extension;  
  
    public File(String name, String extension) {  
        super(name);  
        this.extension = extension;  
    }  
  
    @Override  
    public void display() {  
        System.out.println(name + "(" + extension + ")");  
    }  
  
    public String getExtension() {  
        return extension;  
    }  
  
    public void setExtension(String extension) {  
        this.extension = extension;  
    }  
}

组合节点: 注意children属性

java 复制代码
public class Folder extends FileSystemComponent {  
    private List<FileSystemComponent> children;  
  
    public Folder(String name) {  
        super(name);  
        this.children = new ArrayList<>();  
    }  
  
    @Override  
    public void display() {  
        System.out.println("folder: " + name);  
        for (FileSystemComponent component : children) {  
            component.display();  
        }  
    }  
  
    @Override  
    public void add(FileSystemComponent component) {  
        children.add(component);  
    }  
  
    @Override  
    public void remove(FileSystemComponent component) {  
        children.remove(component);  
    }  
  
    @Override  
    public FileSystemComponent getChild(int index) {  
        if (index >= 0 && index < children.size()) {  
            return children.get(index);  
        }  
        return null;  
    }  
  
    public int getChildCount() {  
        return children.size();  
    }  
  
  
    public List<FileSystemComponent> getChildren() {  
        return new ArrayList<>(children);  
    }  
}

测试和输出:

java 复制代码
public class Client {  
    static void main() {  
        System.out.println("===文件系统示例 ===\n");  
  
        // 创建文件  
        FileSystemComponent file1 = new File("document.txt", "txt");  
        FileSystemComponent file2 = new File("image.jpg", "jpg");  
        FileSystemComponent file3 = new File("data.pdf", "pdf");  
        FileSystemComponent file4 = new File("program.exe", "exe");  
        FileSystemComponent file5 = new File("config.ini", "ini");  
  
        // 创建文件夹  
        Folder root = new Folder("我的电脑");  
        Folder documents = new Folder("文档");  
        Folder images = new Folder("图片");  
        Folder system = new Folder("系统");  
  
        // 构建文件夹结构  
        documents.add(file1);  
        documents.add(file3);  
  
        images.add(file2);  
  
        system.add(file4);  
        system.add(file5);  
  
        root.add(documents);  
        root.add(images);  
        root.add(system);  
  
        // 显示文件系统结构  
        System.out.println("1. 完整的文件系统结构:");  
        root.display();  
    }  
}
复制代码
===文件系统示例 ===

完整的文件系统结构:
folder: 我的电脑
folder: 文档
document.txt(txt)
data.pdf(pdf)
folder: 图片
image.jpg(jpg)
folder: 系统
program.exe(exe)
config.ini(ini)

4. 优缺点

4.1 优点

符合开闭原则 :添加新类型的组件(如链接文件)无需修改现有代码
提高复用性 :可以复用叶子节点和组合节点,构建复杂的层次结构
增强可维护性 :简化客户端代码,客户端无需关心是单个对象还是组合对象
提高可读性 :通过树形结构清晰地表达了"部分-整体"的关系
支持递归组合:可以方便地构建任意复杂的对象结构

4.2 缺点

设计较为抽象 :增加了系统的抽象性和理解难度
类型检查问题 :在运行时可能需要类型检查来确定具体类型
性能考虑:对于深层次的树结构,递归操作可能有性能开销

5. 源码分析(JDK中的组合模式实现)

JDK中的map很典型地体现了组合模式思想,map接口中包含了一些列map增删方法,同时putAll方法,可以接受另一个Map

java 复制代码
    public static void main(String[] args) {
        // 叶子
        Map<String, Integer> map1 = new HashMap<>();
        map1.put("A", 1);
        map1.put("B", 2);
        
        // 叶子
        Map<String, Integer> map2 = new HashMap<>();
        map2.put("C", 3);
        
        // 组合
        Map<String, Map<String, Integer>> nestedMap = new HashMap<>();
        nestedMap.put("Nested", map1);
        
        // 
        Map<String, Object> result = new HashMap<>();
        result.putAll(map1);          
        result.putAll(map2);          
        // 添加嵌套Map
        result.put("NestedMap", nestedMap); 
        
        System.out.println(result);

    }

putAll的定义

java 复制代码
public interface Map<K, V> {
    // ... 其他方法
    
    // putAll方法接受另一个Map(可以看作组合对象)
    void putAll(Map<? extends K, ? extends V> m);
}

HashMap实现

java 复制代码
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
    
    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        // 这里的m可以是单个HashMap,也可以是嵌套的Map结构
        // 客户端不需要知道m的具体结构,统一对待
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }
}

分析

  • Map接口充当了抽象组件角色
  • 简单的map实例可以看作是叶子节点
  • 包含其他Map的Map可以看作是组合节点
  • putAll方法允许客户端统一处理单个Map和嵌套的Map结构
  • 客户端代码不需要关心Map内部的具体结构,只需通过统一接口操作

注意 :严格来说,JDK的Map实现不是标准的组合模式,因为它没有明确定义Component、Leaf、Composite的层次结构,但它体现了组合模式的核心思想------统一处理单个对象和组合对象


参考:

相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习
im_AMBER3 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J3 天前
从“Hello World“ 开始 C++
c语言·c++·学习