结合JDK源码看设计模式——组合模式
前言:
相信大家都打开过层级很多很多的文件夹。如果把第一个文件夹看作是树的根节点的话,下面的子文件夹就可以看作一个子节点。不过最终我们寻找的还是文件夹中的文件,文件可以看做是叶子节点。下面我们介绍一种模式,与这种树级结构息息相关。当然,今天的主角是HashMap。接下来我们一起来看HashMap中到底是怎么跟树级结构进行挂钩的。
一、定义
将对象组合成树形结构以表示“部分-整体”的一个层次结构,使客户端对单个对象和组合对象保持一致的方式处理。
二、适用场景
1、客户端可以忽略组合对象与单个对象的差异
注意组合模式中的概念,当客户端使用组合模式的时候是对单个对象和组合对象保持一致的方式处理,而不是不同的方式处理。所以如果客户端可以忽略组合对象和单个对象的差异时,才用组合模式。
2、处理一个树形结构
这里就很好理解了,组合模式就是处理树形结构的
三、结合HashMap看组合模式
1、抽象构件:总的抽象类或者接口,定义一些通用的方法,比如新增、删除。
2、中间构件:继承或者实现抽象构件,定义存储方式,并针对特殊需要重写抽象构件的方法。
3、叶子节点:继承或者实现抽象构件,并针对特殊需要重写抽象构件的方法。
叶子节点需要实现或者继承抽象构件,如果有需要也可以添加到中间构件上。就比如说我现在进入D盘,D盘就是一个抽象构件,无论在D盘哪里,我都可以执行新建,删除操作。中间构件就相当于文件夹,里面定义了相应的存储方式,加入的文件是叶子节点,并且会按照这种方式来存储,同时这个中间构件也可以选择再加入一个其他中间构件或者自己。叶子节点就可以看作文件。注意,在组合模式下所有类都需要直接或者间接,继承或实现总的抽象构件。下面讲HashMap中的putAll()方法
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float 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);
}
}
}
}
上面是简化的HashMap。我们看见putAll方法传入的是Map对象,Map就是一个抽象构件(同时这个构件中只支持键值对的存储格式),而HashMap是一个中间构件,HashMap中的Node节点就是叶子节点。说到中间构件就会有规定的存储方式。HashMap中的存储方式是一个静态内部类的数组Node<K,V>[] tab。下面是简化的具体代码
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
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;
}
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 1st
treeifyBin(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 key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
}
所以我们调用put方法实际上是加入一个叶子节点,而我们调用putAll相当于把其他Map下面的文件夹中的文件拷贝过来。还有Hashtable也可以看作一个中间构件,里面的putAll方法同样是适合放入实现Map接口的对象。这里如果将Hashtable的这个中间构件放入HashMap中,那么我们这些方法还能用吗。答案是能用,组合模式处理的就是一致性的问题。Map接口中的方法,比如put(),remove(),到下面的中间构件中其实都是一致的功能,不过就是不同的中间构件中的处理方式可能会有细微的差别。下面是我的测试代码
public class Test {
public static void main(String[] args) {
Map<Integer,String> rootmap=new HashMap<Integer,String>();
rootmap.put(1,"HashMap文件1");
Map<Integer,String> map1=new Hashtable<Integer,String>();
map1.put(2,"Hashtable文件1");
map1.put(3,"Hashtable文件2");
map1.put(4,"Hashtable文件3");
rootmap.putAll(map1);
System.out.println(rootmap);
} }
输出结果为:

上面实现将Hashtable中的键值对放入HashMap中。如果放在组合模式下,你就可以看作是将Hashtable这个中间构件下的文件拷贝到HashMap这个中间构件中。在我们电脑里文件任何类型的文件都可以拷到其他任意地方。但是用来理解Map这个抽象构件的就不行,因为实现Map接口的类只支持放入键值对格式的叶子节点,如果不是(比如说ArrayList)就不行,这是底层方法不支持。同时我们回到组合模式上重要的一点:一致性,Map下面添加都是put方法,需要传入两个值,List下都是add方法,只需要传入一个值,如果List中的可以通过调用putAll方法放入Map里,那么我该怎么给List赋key或value呢。(不是说不能有put(1,list)这样的操作,而是说在组合模式下操作时候需要有一致性,并且这里说的组合模式针对的是putAll方法,与其他方法无关)
四、总结
这里只是解析了HashMap中的putAll组合模式。在平常写代码的过程中要使用组合模式,就需要先定义一个抽象类或者接口。在这里就可以看作是抽象构件,主要实现一些基本的通用的功能。接着如果有需要就要定义一个中间构件,并且实现抽象构件,里面的方法可以根据特殊需要重写。而且这个类最重要的是要有存储结构在里面,用Map存储,或者用List存储等等都可以。接着就是我们真正的叶子节点,同样是继承或实现抽象构件,里面的方法可以根据特殊需要重写。当你看完这篇博客,你可以再去看定义和适用场景,说不定收获会更大。
结合JDK源码看设计模式——组合模式的更多相关文章
- 结合JDK源码看设计模式——桥接模式
前言: 在我们还没学习框架之前,肯定都学过JDBC.百度百科对JDBC是这样介绍的[JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Jav ...
- 结合JDK源码看设计模式——原型模式
定义: 指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.不需要知道任何创建的细节,不调用构造函数适用场景: 类初始化的时候消耗较多资源 new产生的对象需要非常繁琐的过程 构造函数比较 ...
- 结合JDK源码看设计模式——模板方法模式
前言: 相信很多人都听过一个问题:把大象关进冰箱门,需要几步? 第一,把冰箱门打开:第二,把大象放进去:第三,把冰箱门关上.我们可以看见,这个问题的答案回答的很有步骤.接下来我们介绍一种设计模式--模 ...
- 结合JDK源码看设计模式——迭代器模式
前言: Iterator翻译过来就是迭代器的意思.在前面的工厂模式中就介绍过了iterator,不过当时介绍的是方法,现在从Iterator接口的设计来看,似乎又是一种设计模式,下面我们就来讲讲迭代器 ...
- 结合JDK源码看设计模式——建造者模式
概念: 将一个复杂对象的构建与它的表示分离.使得同样构建过程可以创建不同表示适用场景: 一个对象有很多属性的情况下 想把复杂的对象创建和使用分离 优点: 封装性好,扩展性好 详解: 工厂模式注重把这个 ...
- 结合JDK源码看设计模式——策略模式
前言: 现在电商已经成为我们生活中不可或缺的购物渠道,同时各大商家会针对不同的时间做出不同的折扣,这在我们看来就是一种营销手段,也是一种策略,今天我们就来讲讲JDK中的策略模式是怎么样的. 一.定义 ...
- 结合JDK源码看设计模式——单例模式
定义: 保证一个类仅有一个实例,并提供一个全局访问点 适用场景: 确保任何情况下这个对象只有一个实例 详解: 私有构造器 单利模式中的线程安全+延时加载 序列化和反序列化安全, 防止反射攻击 结合JD ...
- 结合JDK源码看设计模式——简单工厂、工厂方法、抽象工厂
三种工厂模式的详解: 简单工厂模式: 适用场景:工厂类负责创建的对象较少,客户端只关心传入工厂类的参数,对于如何创建对象的逻辑不关心 缺点:如果要新加产品,就需要修改工厂类的判断逻辑,违背软件设计中的 ...
- 结合JDK源码看设计模式——享元模式
前言 在说享元模式之前,你一定见到过这样的面试题 public class Test { public static void main(String[] args) { Integer a=Inte ...
随机推荐
- 学习CTF的经历-文件分析
文件分析-ZIP伪加密 最近在准备铁人三项赛的比赛,所以在实验吧上尝试着学习CTF,目前菜鸡一枚 我主要负责的是Web和安全杂项这一块,安全杂项的知识点较为薄弱,在实验吧练习的过程中遇到一个很有趣的题 ...
- MySql在生产环境中是用mysqldump还是xtrabackup备份和恢复数据
如题,究竟该使用mysqldump还是xtrabackup,要说用,两个都能备份,都支持热备,但是生产环境我们要考虑的是效率,就是不管备份还是恢复,都要快,要稳定. 之前我在维护mysql数据库的时候 ...
- 如何用Redis做LRU-Cache
LRU(Least Recently Used)最近最少使用算法是众多置换算法中的一种. Redis中有一个maxmemory概念,主要是为了将使用的内存限定在一个固定的大小.Redis用到的LRU ...
- 【C#】对异步请求处理程序IHttpAsyncHandler的理解和分享一个易用性封装
在asp.net项目中,添加一个[一般处理程序]来处理请求是很自然的事,这样会得到一个实现自IHttpHandler的类,然后只需在ProcessRequest方法中写上处理逻辑就行了.但是这样的一个 ...
- Python题库
Date:2018-05-08 1.Given: an array containing hashes of names Return: a string formatted as a list of ...
- Windows下SVN命令行工具使用详解
根据我的记忆,似乎Windows 7下自自带一个svn命令行工具.如果你的机器没有,不必担心.你可以从http://subversion.tigris.org获 取subversion for win ...
- distinct的用法
1. Distinct 位置 单独的distinct只能放在开头,否则报错,语法错误 例:SELECT Sid,DISTINCT(Sscore) score from t_student; [SQL ...
- InfluxDB介绍
InfluxDB介绍 InfluxDB用Go语言编写的一个开源分布式时序.事件和指标数据库,和传统是数据库相比有不少不同的地方. 类似的数据库有Elasticsearch.Graphite等. 特点 ...
- 「WC 2018」州区划分
题目大意: 给一个无向图$G(V,E)$满足$|V|<=21$,对于某一种将$G(V,E)$划分为k个的有序集合方案,若每一个子集$G_i(V_i,E_i)$,$E_i=\{(x,y)|x\in ...
- [POI2007]洪水pow bfs
发现:只在所有自己的城市建水泵一定是最优解. 所以对自己的城市按高度排序,该城市不用建的前提是从他出发经过一条高度都小于等于他的路径能到达一个已经修建水泵的 sort+bfs...... #inclu ...