前言:

  相信大家都打开过层级很多很多的文件夹。如果把第一个文件夹看作是树的根节点的话,下面的子文件夹就可以看作一个子节点。不过最终我们寻找的还是文件夹中的文件,文件可以看做是叶子节点。下面我们介绍一种模式,与这种树级结构息息相关。当然,今天的主角是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源码看设计模式——组合模式的更多相关文章

  1. 结合JDK源码看设计模式——桥接模式

    前言: 在我们还没学习框架之前,肯定都学过JDBC.百度百科对JDBC是这样介绍的[JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Jav ...

  2. 结合JDK源码看设计模式——原型模式

    定义: 指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.不需要知道任何创建的细节,不调用构造函数适用场景: 类初始化的时候消耗较多资源 new产生的对象需要非常繁琐的过程 构造函数比较 ...

  3. 结合JDK源码看设计模式——模板方法模式

    前言: 相信很多人都听过一个问题:把大象关进冰箱门,需要几步? 第一,把冰箱门打开:第二,把大象放进去:第三,把冰箱门关上.我们可以看见,这个问题的答案回答的很有步骤.接下来我们介绍一种设计模式--模 ...

  4. 结合JDK源码看设计模式——迭代器模式

    前言: Iterator翻译过来就是迭代器的意思.在前面的工厂模式中就介绍过了iterator,不过当时介绍的是方法,现在从Iterator接口的设计来看,似乎又是一种设计模式,下面我们就来讲讲迭代器 ...

  5. 结合JDK源码看设计模式——建造者模式

    概念: 将一个复杂对象的构建与它的表示分离.使得同样构建过程可以创建不同表示适用场景: 一个对象有很多属性的情况下 想把复杂的对象创建和使用分离 优点: 封装性好,扩展性好 详解: 工厂模式注重把这个 ...

  6. 结合JDK源码看设计模式——策略模式

    前言: 现在电商已经成为我们生活中不可或缺的购物渠道,同时各大商家会针对不同的时间做出不同的折扣,这在我们看来就是一种营销手段,也是一种策略,今天我们就来讲讲JDK中的策略模式是怎么样的. 一.定义 ...

  7. 结合JDK源码看设计模式——单例模式

    定义: 保证一个类仅有一个实例,并提供一个全局访问点 适用场景: 确保任何情况下这个对象只有一个实例 详解: 私有构造器 单利模式中的线程安全+延时加载 序列化和反序列化安全, 防止反射攻击 结合JD ...

  8. 结合JDK源码看设计模式——简单工厂、工厂方法、抽象工厂

    三种工厂模式的详解: 简单工厂模式: 适用场景:工厂类负责创建的对象较少,客户端只关心传入工厂类的参数,对于如何创建对象的逻辑不关心 缺点:如果要新加产品,就需要修改工厂类的判断逻辑,违背软件设计中的 ...

  9. 结合JDK源码看设计模式——享元模式

    前言 在说享元模式之前,你一定见到过这样的面试题 public class Test { public static void main(String[] args) { Integer a=Inte ...

随机推荐

  1. CentOS 6 安装Hadoop集群

    hadoop2.6.4安装包(链接:https://pan.baidu.com/s/15qHpdoLBQHP4HdxOJzNcsg 密码:5hel) 1.准备Linux环境 1.0先将虚拟机的网络模式 ...

  2. Centos制作本地yum源

    本地YUM源制作 1. YUM相关概念 1.1. 什么是YUM YUM(全称为 Yellow dog Updater, Modified)是一个在Fedora和RedHat以及CentOS中的Shel ...

  3. Robot framework之元素定位实战

    1.1  id 和name 定位 Web页面都是由许多标签和元素组成的,每个标签或元素都是很多属性,好比一个人   id 和name 可以看作一个人的身份证号和姓名.下面看下教育局招生系统的用户名输入 ...

  4. VM虚拟机链接克隆及linux eth0网卡的快速设置方法

    对于后台开发者来说,在学习过程中必然接触众多中间件,在自己的虚拟机进行操作甚至搭建cluster是很常见的事情. 我在初学者时摸索出一套快速的克隆虚拟机方法.分享给大家. 产品VMware® Work ...

  5. vue中keep-alive的用法

    1.keep-alive的作用以及好处 在做电商有关的项目中,当我们第一次进入列表页需要请求一下数据,当我从列表页进入详情页,详情页不缓存也需要请求下数据,然后返回列表页,这时候我们使用keep-al ...

  6. JS 数据类型、赋值、深拷贝和浅拷贝

    js 数据类型 六种 基本数据类型: Boolean. 布尔值,true 和 false. null. 一个表明 null 值的特殊关键字. JavaScript 是大小写敏感的,因此 null 与 ...

  7. JavaWeb(一)JavaWeb应用的概念

    JavaWeb应用的概念 在Sun的Java Servlet规范中,对Java Web应用作了这样定义:"Java Web应用由一组Servlet.HTML页.类.以及其它可以被绑定的资源构 ...

  8. MyBatis-Spring中间件逻辑分析(怎么把Mapper接口注册到Spring中)

    1.      文档介绍 1.1.      为什么要写这个文档 接触Spring和MyBatis也挺久的了,但是一直还停留在使用的层面上,导致很多时候光知道怎么用,而不知道其具体原理,这样就很难做一 ...

  9. Centos7下安装PHP5.5,5.6,7.0----(转载记录一下)

    由于centOS7 默认的php版本是5.4的,偏低,所以收录了一下怎样安装5.5/5.6/7.0版本 默认的版本太低了,手动安装有一些麻烦,想采用Yum安装的可以使用下面的方案: 1.检查当前安装的 ...

  10. Go缓存DNS

    Go里面的DNSclient没有带任何的缓存,语言层面没有任何的缓存,但是我们可以通过下面这样的代码来 这样当我们调用http.Client的时候就会采用这个缓存的ip了,里面用到的dnscache是 ...