news 2026/9/26 4:19:19

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式学习(11) 23-9 组合模式

文章目录

  • 0.个人感悟
  • 1. 概念
  • 2. 适配场景
    • 2.1 适合的场景
    • 2.2 常见场景举例
  • 3. 实现方法
    • 3.1 实现思路
    • 3.2 UML类图
    • 3.3 代码示例
  • 4. 优缺点
    • 4.1 优点
    • 4.2 缺点
  • 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 代码示例

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

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

publicabstractclassFileSystemComponent{protectedStringname;publicFileSystemComponent(Stringname){this.name=name;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){}/** * @description display * @author bigHao * @date 2026/1/11 **/publicabstractvoiddisplay();/** * @param component 组件 * @description 添加操作 文件不支持 * @author bigHao * @date 2026/1/11 **/publicvoidadd(FileSystemComponentcomponent){thrownewUnsupportedOperationException("不支持添加操作");}/** * @param component 组件 * @description 移除操作,文件不支持 * @author bigHao * @date 2026/1/11 **/publicvoidremove(FileSystemComponentcomponent){thrownewUnsupportedOperationException("不支持删除操作");}/** * @param index 索引 * @return FileSystemComponent 子对象 * @description 获取子对象 文件不支持 * @author bigHao * @date 2026/1/11 **/publicFileSystemComponentgetChild(intindex){thrownewUnsupportedOperationException("不支持获取子组件操作");}}

叶子节点:

publicclassFileextendsFileSystemComponent{privateStringextension;publicFile(Stringname,Stringextension){super(name);this.extension=extension;}@Overridepublicvoiddisplay(){System.out.println(name+"("+extension+")");}publicStringgetExtension(){returnextension;}publicvoidsetExtension(Stringextension){this.extension=extension;}}

组合节点: 注意children属性

publicclassFolderextendsFileSystemComponent{privateList<FileSystemComponent>children;publicFolder(Stringname){super(name);this.children=newArrayList<>();}@Overridepublicvoiddisplay(){System.out.println("folder: "+name);for(FileSystemComponentcomponent:children){component.display();}}@Overridepublicvoidadd(FileSystemComponentcomponent){children.add(component);}@Overridepublicvoidremove(FileSystemComponentcomponent){children.remove(component);}@OverridepublicFileSystemComponentgetChild(intindex){if(index>=0&&index<children.size()){returnchildren.get(index);}returnnull;}publicintgetChildCount(){returnchildren.size();}publicList<FileSystemComponent>getChildren(){returnnewArrayList<>(children);}}

测试和输出:

publicclassClient{staticvoidmain(){System.out.println("===文件系统示例 ===\n");// 创建文件FileSystemComponentfile1=newFile("document.txt","txt");FileSystemComponentfile2=newFile("image.jpg","jpg");FileSystemComponentfile3=newFile("data.pdf","pdf");FileSystemComponentfile4=newFile("program.exe","exe");FileSystemComponentfile5=newFile("config.ini","ini");// 创建文件夹Folderroot=newFolder("我的电脑");Folderdocuments=newFolder("文档");Folderimages=newFolder("图片");Foldersystem=newFolder("系统");// 构建文件夹结构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

publicstaticvoidmain(String[]args){// 叶子Map<String,Integer>map1=newHashMap<>();map1.put("A",1);map1.put("B",2);// 叶子Map<String,Integer>map2=newHashMap<>();map2.put("C",3);// 组合Map<String,Map<String,Integer>>nestedMap=newHashMap<>();nestedMap.put("Nested",map1);//Map<String,Object>result=newHashMap<>();result.putAll(map1);result.putAll(map2);// 添加嵌套Mapresult.put("NestedMap",nestedMap);System.out.println(result);}

putAll的定义

publicinterfaceMap<K,V>{// ... 其他方法// putAll方法接受另一个Map(可以看作组合对象)voidputAll(Map<?extendsK,?extendsV>m);}

HashMap实现

publicclassHashMap<K,V>extendsAbstractMap<K,V>implementsMap<K,V>,Cloneable,Serializable{@OverridepublicvoidputAll(Map<?extendsK,?extendsV>m){// 这里的m可以是单个HashMap,也可以是嵌套的Map结构// 客户端不需要知道m的具体结构,统一对待for(Map.Entry<?extendsK,?extendsV>e:m.entrySet())put(e.getKey(),e.getValue());}}

分析:

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

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


参考:

  • 韩顺平 Java设计模式
  • 张维鹏 Java设计模式之结构型:组合模式
  • kosamino 设计模式之组合模式(Composite)详解及代码示例
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 11:52:57

Vivado2021.1安装教程:集成SDK的完整环境搭建

Vivado 2021.1 安装实战&#xff1a;从零搭建带 SDK 的 FPGA 开发环境 你是不是正准备开始 FPGA 项目&#xff0c;却被一堆安装文档搞得头大&#xff1f;尤其是看到“Vivado SDK”这种组合时&#xff0c;总担心漏掉哪一步会导致后面软件打不开、工程编译失败&#xff1f; 别…

作者头像 李华
网站建设 2026/9/25 14:46:02

入门必看:Windows平台下C#上位机开发起步

从零开始&#xff1a;用C#打造你的第一款工业级上位机你有没有过这样的经历&#xff1f;手里的单片机已经能采集温度、读取传感器数据&#xff0c;但想实时监控却只能靠串口助手“看数字”&#xff1f;调试时满屏乱跳的十六进制让人头大&#xff0c;客户更是一脸茫然&#xff1…

作者头像 李华
网站建设 2026/9/25 14:46:03

模拟电路设计验证:电路仿真的关键应用

模拟电路设计的“数字沙盘”&#xff1a;为什么仿真决定成败你有没有经历过这样的场景&#xff1f;花了几周时间画好原理图、打样PCB、焊好元件&#xff0c;结果一通电——输出电压不对&#xff0c;运放自激振荡&#xff0c;或者噪声大得像收音机调台。更糟的是&#xff0c;问题…

作者头像 李华
网站建设 2026/9/25 14:46:03

数字电路实验中的逻辑门优化策略深度剖析

数字电路实验中的逻辑门优化&#xff1a;从卡诺图到FPGA的实战精要在数字电路实验室里&#xff0c;你是否曾面对一堆74系列芯片和错综复杂的跳线感到头大&#xff1f;明明功能实现了&#xff0c;但电路板上密密麻麻的连线让人怀疑自己是不是在“绣花”&#xff1b;更糟的是&…

作者头像 李华
网站建设 2026/9/20 11:39:12

Terraform字符串操作:巧妙处理Azure容器注册表域名

在使用Terraform进行基础设施即代码&#xff08;Infrastructure as Code&#xff09;时&#xff0c;字符串操作是常见的需求。本文将通过一个具体的实例&#xff0c;介绍如何利用Terraform的字符串函数来修改Azure容器注册表&#xff08;Azure Container Registry, ACR&#xf…

作者头像 李华
网站建设 2026/9/25 14:46:04

动态更新Mat表格的技巧与实例

在使用Angular Material的Mat表格时,经常会遇到需要在添加新数据后动态更新表格的问题。尤其是当我们使用对话框(Dialog)模块来添加新数据时,表格的更新变得尤为复杂。本文将通过实例讲解如何在对话框添加新数据后,成功更新Mat表格。 背景介绍 假设我们有一个产品管理系…

作者头像 李华