15.一种坍缩式的简单——组合模式详解
当曾经的孩子们慢慢步入社会才知道,那年味渐淡的春节就像是疾驰在人生路上的暂停键。
它允许你在隆隆的鞭炮声中静下心来,瞻前顾后,怅然若失。
也允许你在寂静的街道上屏气凝神,倾听自己胸腔里的那团人声鼎沸。
孩子们会明白的,就像他们步入大学校园时候渐渐明白家乡只有冬夏,再无春秋一样。
人生这场旅途,就是无数个后知后觉的组合。提前看到一些东西不会让我们清醒半分,相反,这一切反而容易让人愈发的沉溺于那来势汹汹的纸醉金迷…
正月初九,对于多数打工人来说是真正意义上新的一年的开始,就像是一条条沉睡的金鱼被人从湖底捞起再次丢进那个金碧辉煌的鱼缸,开始在整个体系内扮演不同的角色。组织架构、行政管理、经理、开发、部署、运维、测试…
今天,我们不妨就着组织架构这个话题,来拆解下结构型设计模式中将聚合思路用到极致的实现方案:组合模式。
一言
组合模式通过创建对象组的的树形结构,让客户以一致的方式处理个别对象以及组合对象。
当组织开始优化

概念和思路是为需求服务的,就比如:
我创办了一家名为“Wayne实业”的集团公司,为了集团战略的更好落实,我对组织结构进行了优化。集团公司下设多个省级分公司,每个省级分公司下设地市级办事处。
现在我要求你在集团公司网页上级联的展示架构信息,有什么思路?
探路式思考
首先明确下,我们不是在具体讨论用Vue或React等成熟前端框架的某个组件更优,而是从实体设计的角度去思考。
相信很多朋友最先想到的就是继承关系,分公司作为集团公司的子类,办事处作为分公司的子类。诚然,需求的描述实在是太契合继承关系的特征了。

但是我们仔细分析下需求就会发现,这种设计方式实际上存在很大的问题。集团公司有多个分公司,每个分公司下又有大量的办事处。这种设计方式非常不利于对分公司、办事处的管理。可以思考下,一旦需要做遍历或增删操作要投入多大的成本?
组合模式的思考
其实,组合模式非常善于解决这样的问题。当我们要处理的对象可以凭借需求进化成一棵树的时候,这一切反而变得简单。
相信很多朋友读到这里会有疑问,树结构在编程中其实并不是一个容易处理的结构,为什么还说它简单?
这是因为在组合模式下,我们对树上的节点或者叶子的操作都是扁平的,根本不用考虑它是节点还是叶子。
就好像一个立体的三维空间突然坍缩成一个平面,我们需要关注的东西突然少了一个维度,简单的幸福由此蔓延开来。
设计

代码实现
核心
public abstract class OrganizationComponent {private String name ;private String des;protected void add(OrganizationComponent organizationComponent){throw new UnsupportedOperationException();}protected void remove(OrganizationComponent organizationComponent){throw new UnsupportedOperationException();}protected abstract void print();public OrganizationComponent(String name, String des) {this.name = name;this.des = des;}//setter&getter
}
集团公司
public class WayneIndustries extends OrganizationComponent{List<OrganizationComponent> organizationComponents = new ArrayList<>();public WayneIndustries(String name, String des) {super(name, des);}@Overrideprotected void add(OrganizationComponent organizationComponent) {organizationComponents.add(organizationComponent);}@Overrideprotected void remove(OrganizationComponent organizationComponent) {organizationComponents.remove(organizationComponent);}@Overridepublic String getName() {return super.getName();}@Overridepublic String getDes() {return super.getDes();}@Overrideprotected void print() {System.out.println("--------------------"+getName()+"----------------------");for (OrganizationComponent ogc : organizationComponents)ogc.print();}
}
省公司
public class ProvincialCompany extends OrganizationComponent{List<OrganizationComponent> organizationComponents = new ArrayList<>();public ProvincialCompany(String name, String des) {super(name, des);}@Overrideprotected void add(OrganizationComponent organizationComponent) {organizationComponents.add(organizationComponent);}@Overrideprotected void remove(OrganizationComponent organizationComponent) {organizationComponents.remove(organizationComponent);}@Overridepublic String getName() {return super.getName();}@Overridepublic String getDes() {return super.getDes();}@Overrideprotected void print() {System.out.println("--------------------"+getName()+"----------------------");for (OrganizationComponent ogc : organizationComponents)ogc.print();}
}
办事处
public class Office extends OrganizationComponent{public Office(String name, String des) {super(name, des);}@Overridepublic String getName() {return super.getName();}@Overridepublic String getDes() {return super.getDes();}@Overrideprotected void print() {System.out.println(getName());}
}
客户端
public class Client {public static void main(String[] args) {OrganizationComponent wayne = new WayneIndustries("Wayne实业", "世界500强");OrganizationComponent company1 = new ProvincialCompany("江苏分公司", "综合贸易");OrganizationComponent company2 = new ProvincialCompany("广东分公司", "对外贸易");company1.add(new Office("徐州办事处", "主营冶金"));company1.add(new Office("连云港办事处", "海港事宜"));company1.add(new Office("镇江办事处", "旅游业"));company2.add(new Office("深圳办事处","对外贸易"));company2.add(new Office("珠海办事处","对澳贸易"));wayne.add(company1);wayne.add(company2);wayne.print();
// company1.print();
// company2.print();}
}
测试

组合模式在JDK源码中的应用
笔者在学习设计模式的过程中,有一个很直观的感受就是:设计模式给了普通开发者另一个视角来审视问题,也使得我们看待某些问题变得更灵活。比如顶层抽象,它可以依托于接口也可以依托于抽象类,再比如泛化关系,它可以依托于继承和实现,也可以依托于静态内部类。
这种思维方式的提升就像一个兢兢业业在工地上码砖的工人突然间抬头打量起整个房间的布局甚至整栋大厦的结构原理,那种豁然开朗的感觉或许只有亲历者才能感同身受。
在JDK源码中,组合模式也十分常见。比如我们熟知的HashMap:
Map<Integer,String> hashMap = new HashMap<>();hashMap.put(0,"江苏");Map<Integer,String> map = new HashMap<>();map.put(1,"浙江");map.put(2,"广东");hashMap.putAll(map);System.out.println(hashMap);
在HashMap中,既可以用put加入一个键值对,也可以用putAll加入多个键值对。

我们通过审视HashMap的结构可以发现它为了扩展性不但继承了抽象AbstractMap的同时也实现了Map接口,这两个抽象实际上都可以看作是组合模式的OrganizationComponent(也就是我之前例子中的集团公司)。而有读过HashMap源码的朋友应该知道,在HashMap中有个及其关键的静态内部类Node。
相关源码片:
/*** Basic hash bin node, used for most entries. (See below for* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)*/static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}public final K getKey() { return key; }public final V getValue() { return value; }public final String toString() { return key + "=" + value; }public final int hashCode() {return Objects.hashCode(key) ^ Objects.hashCode(value);}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (o == this)return true;if (o instanceof Map.Entry) {Map.Entry<?,?> e = (Map.Entry<?,?>)o;if (Objects.equals(key, e.getKey()) &&Objects.equals(value, e.getValue()))return true;}return false;}}
它是组成链表/红黑树的原子结构(这里与本节无关暂不做展开,感兴趣的朋友可以再查阅下相关资料),hashMap的put方法、putAll方法都以Node为最小执行单位。这样来看,静态内部类Node更像是由HashMap泛化来的另一个实现形式(类似于我之前例子中的省公司、办事处)。这种叶子节点(Node)与子节点(HashMap)同根同源(Map),与组合模式理念“不谋而合”。

相关源码片:
/*** Associates the specified value with the specified key in this map.* If the map previously contained a mapping for the key, the old* value is replaced.** @param key key with which the specified value is to be associated* @param value value to be associated with the specified key* @return the previous value associated with <tt>key</tt>, or* <tt>null</tt> if there was no mapping for <tt>key</tt>.* (A <tt>null</tt> return can also indicate that the map* previously associated <tt>null</tt> with <tt>key</tt>.)*/public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}/*** Implements Map.put and related methods.** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}/*** Copies all of the mappings from the specified map to this map.* These mappings will replace any mappings that this map had for* any of the keys currently in the specified map.** @param m mappings to be stored in this map* @throws NullPointerException if the specified map is null*/public void putAll(Map<? extends K, ? extends V> m) {putMapEntries(m, true);}/*** Implements Map.putAll and Map constructor.** @param m the map* @param evict false when initially constructing this map, else* true (relayed to method afterNodeInsertion).*/final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {int s = m.size();if (s > 0) {if (table == null) { // pre-sizefloat ft = ((float)s / loadFactor) + 1.0F;int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);if (t > threshold)threshold = tableSizeFor(t);}else if (s > threshold)resize();for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {K key = e.getKey();V value = e.getValue();putVal(hash(key), key, value, false, evict);}}}
结
组合模式依据树形结构来组合对象,从部分到整体层次分明,是典型的结构型模式。组合模式对用户使用的单个对象表现出极强的一致性,对客户而言无需特别关注个别对象和组合对象。
希望此文能够让大家对组合模式有更进一步的理解。
关注我,共同进步,每周至少一更。——Wayne
相关文章:
15.一种坍缩式的简单——组合模式详解
当曾经的孩子们慢慢步入社会才知道,那年味渐淡的春节就像是疾驰在人生路上的暂停键。 它允许你在隆隆的鞭炮声中静下心来,瞻前顾后,怅然若失。 也允许你在寂静的街道上屏气凝神,倾听自己胸腔里的那团人声鼎沸。 孩子们会明白的&am…...
Node.js的debug模块源码分析及在harmonyOS平台移植
Debug库 是一个小巧但功能强大的 JavaScript 调试工具库,可以帮助开发人员更轻松地进行调试,以便更快地发现和修复问题。它的主要特点是可以轻松地添加调试日志语句,同时在不需要调试时可以轻松地禁用它们,以避免在生产环境中对性…...
【Crypto | CTF】BUUCTF RSA2
天命:密码学越来越难了,看别人笔记都不知道写啥 天命:莫慌,虽然我不会推演法,但我可以用归纳法 虽然我不知道解题的推演,但我可以背公式啊哈哈哈 虽然我不会这题,但是我也能做出来 公式我不知…...
单片机学习笔记---红外遥控红外遥控电机调速(完结篇)
目录 低电平触发中断和下降沿触发中断的区别 红外遥控 Int0.c Int.h Timer0.c Timer0.h IR.c IR.h main.c 红外遥控电机调速 Timer1.c Timer.h Motor.c Motor.h main.c 上一节讲了红外发送和接收的工作原理,这一节开始代码演示! 提前说…...
Linux第62步_备份移植好的所有的文件和文件夹
1、备份“my-tfa”目录下所有的文件和文件夹 1)、打开终端 输入“ls回车”,列出当前目录下所有的文件和文件夹 输入“cd linux回车”,切换“linux”目录下 输入“ls回车”,列出当前目录下所有的文件和文件夹 输入“cd atk-mp1/回车”&am…...
【xss跨站漏洞】xss漏洞前置知识点整理
xss漏洞成因 xss漏洞是一种前端javascript产生的漏洞。 我们网站基本都是会用到javascript编写一些东西,浏览器也能直接识别javascript。 如果有一个地方能够输入文字,但是他又没有过滤你的输入,那么自己或者他人看到你输入的javascript代…...
mac下mysql 常用命令
mysql启动命令 在Mac OS X启动和停止MySQL服务的命令, 启动MySQL服务 sudo /usr/local/mysql/support-files/mysql.server start 停止MySQL服务 sudo /usr/local/mysql/support-files/mysql.server stop 重启MySQL服务 sudo /usr/local/mysql/support-files/mys…...
2.21号qt
1.QMainWindow中常用的类 继承于QMainWindow类,原因该类提供了QWidget没有提供的成员函数。 菜单栏、工具栏、状态栏、浮动窗口(铆接部件)、核心部件 1.1 菜单栏 QMenuBar //创建菜单栏 QMenuBar 最多只能有一个 QMenuBar *mbar menu…...
什么是MVVM?MVC、MVP与MVVM模式的区别?
MVVM(Model-View-ViewModel)是一种软件架构模式,用于将用户界面(View)与业务逻辑(Model)分离,并通过ViewModel来连接两者。MVVM的目标是实现可测试性、可维护性和可复用性。 MVC&am…...
ElementUI组件的安装和使用
Element UI 是一款基于 Vue 2.0 的桌面端组件库,主要用于快速构建网站的前端部分。它提供了丰富的组件,如按钮、输入框、表格、标签页等,以及一些布局元素,如布局容器、分割线等。Element UI 的设计风格简洁,易于上手&…...
Laravel01 课程介绍以及Laravel环境搭建
Laravel01 课程介绍 1. Laravel2. mac开发环境搭建(通过Homebrew)3. 创建一个项目 1. Laravel 公司中面临着PHP项目与Java项目并行,所以需要我写PHP的项目,公司用的框架就是Laravel,所以在B站上找了一门课学习。 Laravel中文文档地址 https…...
面试redis篇-03缓存击穿
原理 缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮 解决方案一:互斥锁 解决方案二:逻辑过期 提问与回答 面试官 :什么是缓存击穿 ? 怎么解决 ? 回答: 缓存击穿的意思…...
k8s容器以及基础设施优化
1.硬件系统选型:宿主机通用配置16c/32GB/4网卡队列 2.os优化:单机支持百万tcp并发,/etc/sysctl.conf,/etc/security/limits.conf 3.k8s&容器层优化:性能优化initContainer 4.kube-dns优化:增大--cache-size,设置…...
蓝桥杯备赛系列——倒计时50天!
蓝桥杯备赛系列 倒计时50天! 前缀和和差分 知识点 **前缀和数组:**假设原数组用a[i]表示,前缀和数组用sum[i]表示,那么sum[i]表示的是原数组前i项之和,注意一般用前缀和数组时,原数组a[i]的有效下标是从…...
jenkins配置ssh的时候测试连接出现Algorithm negotiation fail
背景:当jenkins升级后,同时ssh插件也升级,测试ssh连接的时候 出现的问题: com.jcraft.jsch.JSchAlgoNegoFailException: Algorithm negotiation fail: algorithmName"server_host_key" jschProposal"ecdsa-sha2-n…...
思维模型整合
思维模型整合 4P--- 4C思考模型能力圈模型 4P— 4C思考模型 在竞争激烈的今天,每个赛道都有众多可以为客户提供相同价值的对手,而赛道中的佼佼者之所以能打败大部分人,可能并不是他们能比别人更能讨好大众,而是因为在这个赛道它有…...
代理模式笔记
代理模式 代理模式代理模式的应用场景先理解什么是代理,再理解动静态举例举例所用代码 动静态的区别静态代理动态代理 动态代理的优点代理模式与装饰者模式的区别 代理模式 代理模式在设计模式中是7种结构型模式中的一种,而代理模式有分动态代理&#x…...
手机中有哪些逆向进化的功能
手机中有哪些逆向进化的功能?逆向进化是指明明很优秀的很方便的功能,却因为成本或者其他工业原因莫名其妙地给取消了。 逆向进化1:可拆卸电池-变为不可拆卸电池。 智能手机为了追求轻薄等原因,所以移除了可拆卸电池功能。将电池…...
LeetCode24.两两交换链表中的节点
参考链接:代码随想录:LeetCode24.两两交换链表中的节点 我这里使用了3个变量进行暴力交换,简单快捷!但是有一点想不明白,return这里只能写dh->next,写返回head就结果不对了!但是后面又想明白了ÿ…...
Eureka注册中心(黑马学习笔记)
Eureka注册中心 假如我们的服务提供者user-service部署了多个实例,如图: 大家思考几个问题: order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口? 有多个user-service实例地址,…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...


