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实例地址,…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

若依登录用户名和密码加密
/*** 获取公钥:前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...

初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)
零、关于开发思路 (一)拿到工作任务,先理清楚需求 1.逻辑部分 不放过原型里说的每一句话,有疑惑的部分该问产品/测试/之前的开发就问 2.页面部分(含国际化) 整体看过需要开发页面的原型后,分类一下哪些组件/样式可以复用,直接提取出来使用 (时间充分的前提下,不…...

使用ch340继电器完成随机断电测试
前言 如图所示是市面上常见的OTA压测继电器,通过ch340串口模块完成对继电器的分路控制,这里我编写了一个脚本方便对4路继电器的控制,可以设置开启时间,关闭时间,复位等功能 软件界面 在设备管理器查看串口号后&…...