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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/475810.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯】算法模板题(Floyd算法)

一.弗洛伊德算法 用途&#xff1a;用来求解多源点最短路径问题。 思想&#xff1a;Floyd算法又称为插点法&#xff0c;是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法。 主要步骤&#xff1a; 1&#xff09;初始化&#xff1a;使用邻接矩阵初始化dis…

MySQL为什么改进LRU算法?

普通LRU算法 LRU = Least Recently Used(最近最少使用): 就是末尾淘汰法,新数据从链表头部加入,释放空间时从末尾淘汰. 当要访问某个页时,如果不在Buffer Pool,需要把该页加载到缓冲池,并且把该缓冲页对应的控制块作为节点添加到LRU链表的头部。当要访问某个页时,如果在…

单主模式和多主模式切换

1 组复制模式切换注意点 组复制有两种运行模式&#xff0c;一种是单主模式&#xff0c;一种是多主模式。这个模式是在整个组中设置的&#xff0c;由 group_replication_single_primary_mode 这个系统变量指定&#xff0c;而且在所有成员上必须保持一致。ON 表示单主模式&#…

Javaweb之SpringBootWeb案例之AOP通知顺序的详细解析

3.2 通知顺序 讲解完了Spring中AOP所支持的5种通知类型之后&#xff0c;接下来我们再来研究通知的执行顺序。 当在项目开发当中&#xff0c;我们定义了多个切面类&#xff0c;而多个切面类中多个切入点都匹配到了同一个目标方法。此时当目标方法在运行的时候&#xff0c;这多…

包教包会的Kotlin Flow教程

原文链接 包教包会的Kotlin Flow教程 公众号「稀有猿诉」 Kotlin中的Flow是专门用于处理异步数据流的API&#xff0c;是函数响应式编程范式(Functional Reactive Programming FRP)在Kotlin上的一个实现&#xff0c;并且深度融合了Kotlin的协程。是Kotlin中处理异步数据…

文件上传漏洞--Upload-labs--Pass02--Content-Type绕过

一、什么是 Content-Type 我们在上传文件时利用 Burpsuite 进行抓包&#xff0c;如下图所示&#xff1a; 上传文件后台的源代码可能会对 Content-Type 进行规定&#xff0c;设置白名单 或 黑名单&#xff0c;这时就要利用Content-Type绕过上传含有恶意代码的 php文件。 二、代…

1.3_1 操作系统的运行机制

文章目录 1.3_1 操作系统的运行机制&#xff08;一&#xff09;预备知识&#xff1a;程序是如何运行的&#xff08;二&#xff09;内核程序 vs 应用程序&#xff08;三&#xff09;特权指令 vs 非特权指令&#xff08;四&#xff09;内核态 vs 用户态内核态、用户态的切换 总结…

PCB差模辐射是如何产生的

在电路应用中,高频时钟信号往往会采用差分线传输模式,其优点是在提高速率的同时减小功耗和提高抗扰度,因此,差模辐射就成为电路正常工作的结果,是电流流过导体形成的环路所产生,差模辐射模型可以被模拟为一个小环形天线,对于一个面积为A的小环路,载有电流Idm,在远场中…

程序员可以做一辈子吗?大龄程序员出路在哪?

前言 随着2023年AI的出现&#xff0c;大家对待程序员工作有了一丝丝危机感&#xff0c;特别是今年整个IT行业进入了前所未有的寒冬期&#xff0c;让程序员不得不思考未来的职业发展。 甚至很多程序员一想到自己接近35岁&#xff0c;焦虑感油然而生&#xff0c;这也是大部分程…

WebService接口测试

WebService的理解 WebService就是Web服务的意思&#xff0c;对应的应用层协议为SOAP&#xff08;相当于HTTP协议&#xff09;&#xff0c;可理解为远程调用技术。 特点&#xff1a; 客户端发送的请求主体内容&#xff08;请求报文&#xff09;的格式为XML格式 接口返回的响…

代码随想录第33天|● 1005.K次取反后最大化的数组和 ● 134. 加油站 ● 135. 分发糖果

文章目录 1005.K次取反后最大化的数组和贪心思路&#xff1a;代码&#xff1a; 34. 加油站思路一&#xff1a;全局贪心代码&#xff1a; 思路二&#xff1a;代码&#xff1a; 135. 分发糖果思路&#xff1a;两边考虑代码&#xff1a; 1005.K次取反后最大化的数组和 贪心思路&am…

【图像分割 2024 ICLR】Conv-LoRA

【图像分割 2024 ICLR】Conv-LoRA 论文题目&#xff1a;CONVOLUTION MEETS LORA: PARAMETER EFFICIENT FINETUNING FOR SEGMENT ANYTHING MODEL 中文题目&#xff1a;卷积满足lora:分段任意模型的参数有效微调 论文链接&#xff1a;https://arxiv.org/abs/2401.17868 论文代码&…