设计模式学习(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 小时前
java学习--什么是线程安全和不安全
java·学习·安全
Engineer邓祥浩2 小时前
设计模式学习(13) 23-11 享元模式
学习·设计模式·享元模式
week_泽2 小时前
第3课:构建AI代理系统面临的挑战 - 学习笔记_3
人工智能·笔记·学习·ai agent
week_泽2 小时前
第8课:LangGraph Memory管理机制与实现方案 - 学习笔记_8
java·笔记·学习·ai agent
MhZhou04122 小时前
开源 动态课程学习的单细胞聚类
学习
Flamingˢ2 小时前
Verilog中reg与wire的区别:从语法到实战
学习·fpga开发·硬件工程
heartbeat..2 小时前
Spring Boot 学习:原理、注解、配置文件与部署解析
java·spring boot·学习·spring
信奥胡老师2 小时前
P14917 [GESP202512 五级] 数字移动
开发语言·数据结构·c++·学习·算法
深情的小陈同学2 小时前
工作学习笔记 —— 解决刷新缓存问题
笔记·学习·ai编程