I walk very slowly, but I never walk backwards
设计模式 - 组合模式
寂然
大家好,我是寂然,本节课,我们来聊设计模式中的组合模式,老规矩,首先我们先通过一个案例需求来引入
案例演示 - 院校展示
在一个页面中展示出学校的院系组成
一个学校会有多个学院, 一个学院有会多个专业
编写程序,完成需求
解决方案一:一般实现
如果用最容易想到的方式,会做成继承关系,我们定义一个学校,学校下面有各个学院子类,而每个学院有各个专业,来展示页面,这样实际上是站在组织大小的角度来进行分层的,类图如下所示
但是大家考虑,学校和学院之间,真的是一个继承关系吗 ? 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个专业, 因此这种方案,不能很好实现的管理的操作,比如对学院专业的添加,删除,遍历等
其实更合适的一种方式是学校包含学院,学院包含专业,应该是一种组合关系,我们可以换一种思路, 把学校、学院、专业都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作,其实这种思路就是组合模式,下面,我们一起来看下组合模式的基本介绍
基本介绍
组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示 “整体-部分” 的层次关系
组合模式依据树形结构来组合对象,用来表示部分以及整体层次
组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象
原理类图
下面,我们一起来看下组合模式的原理类图,并梳理下组合模式中的角色
组合模式角色
-
Component:这是组合模式中的对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理 Component子部件,Component 可以是抽象类或者接口
-
Leaf : 在组合中表示叶子节点,叶子节点没有子节点 ,是最末端的存放数据的结构
-
Composite:非叶子节点, 用于存储子部件,在 Component 接口中实现子部件的相关操作,比如增加(add),删除 (remove)
组合模式解决的问题
组合模式解决这样的场景,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,组合模式能够提供一致的方式,而不用考虑它是节点还是叶子
解决方案二:组合模式
类图展示
下面,我们使用组合模式解决院校展示问题,首先,我们先来画原理类图来分析思路
代码演示
/**
* @Classname OrganizationComponent
* @Created by 寂然
* @Description I walk very slowly, but I never walk backwards
*/
public abstract class OrganizationComponent {
private String name;
private String desc; //描述
public void add(OrganizationComponent component){
//默认实现 抛出一个不支持操作的异常
throw new UnsupportedOperationException();
}
public void remove(OrganizationComponent component){
//默认实现 抛出一个不支持操作的异常
throw new UnsupportedOperationException();
}
//构造器
public OrganizationComponent(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
//展示方法,做成抽象的
public abstract void show();
}
/**
* @Classname School
* @Created by 寂然
* @Description School也就是Composite ,可以管理College
*/
public class School extends OrganizationComponent {
//构造器
public School(String name, String desc) {
super(name, desc);
}
//组合的是学院
List<OrganizationComponent> componentList = new ArrayList<>();
//重写add方法
@Override
public void add(OrganizationComponent component) {
componentList.add(component);
}
//重写remove方法
@Override
public void remove(OrganizationComponent component) {
componentList.remove(component);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDesc() {
return super.getDesc();
}
@Override
public void show() {
System.out.println("--- " + getName() + "---");
//遍历 componentList
for (OrganizationComponent organizationComponent : componentList) {
organizationComponent.show();
}
}
}
/**
* @Classname College
* @Created by 寂然
* @Description I walk very slowly, but I never walk backwards
*/
public class College extends OrganizationComponent {
public College(String name, String desc) {
super(name, desc);
}
//组合的专业
List<OrganizationComponent> componentList = new ArrayList<>();
//重写add方法
@Override
public void add(OrganizationComponent component) {
//将来实际业务中,Collage和School的添加方法不相同,都有各自的业务逻辑
componentList.add(component);
}
//重写remove方法
@Override
public void remove(OrganizationComponent component) {
componentList.remove(component);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDesc() {
return super.getDesc();
}
@Override
public void show() {
System.out.println("--- " + getName() + "---");
//遍历 componentList
for (OrganizationComponent organizationComponent : componentList) {
organizationComponent.show();
}
}
}
/**
* @Classname Major
* @Created by 寂然
* @Description 专业
*/
public class Major extends OrganizationComponent{
public Major(String name, String desc) {
super(name, desc);
}
//本案例中,Major是叶子节点,不会包含其他的,所以不需要add,remove方法
@Override
public String getName() {
return super.getName();
}
@Override
public String getDesc() {
return super.getDesc();
}
@Override
public void show() {
System.out.println(getName());
}
}
/**
* @Classname Client
* @Created by 寂然
* @Description 客户端
*/
public class Client {
public static void main(String[] args) {
//范围从大到小创建对象
OrganizationComponent school = new School("清华大学", "中国顶级学府");
//创建学院,并添加到学校中
OrganizationComponent computerCollege = new College("计算机学院", "计算机学院");
OrganizationComponent infoCollege = new College("信息工程学院", "信息工程学院");
school.add(computerCollege);
school.add(infoCollege);
//创建各个学院下面的系 并加入到学院中
computerCollege.add(new Major("计算机科学与技术","计算机科学与技术"));
computerCollege.add(new Major("软件工程","软件工程"));
computerCollege.add(new Major("网络空间安全","网络空间安全"));
infoCollege.add(new Major("通信与信息系统","通信与信息系统"));
infoCollege.add(new Major("信号与信息处理","信号与信息处理"));
infoCollege.add(new Major("信息网络与复杂系统","信息网络与复杂系统"));
school.show();
}
}
假设我不关心整个大学的,我只关心某个学院的情况,只需要调用对应学院的 show() 方法即可,便捷的同时非常灵活,如果我们在其中新增一个层级关系,只需要继承 OrganizationComponent,进行聚合,重写里面的业务方法即可,(add,remove,show),所以我们来聊聊使用组合模式解决案例问题的优点
组合模式优势
-
组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,屏蔽了对象系统的层次差异性,使用一致的行为控制不同层次
-
扩展性非常高,可以很方便地增加 树枝节点 和 叶子节点 对象,并对现有类库无侵入,满足“开闭原则”
组合模式缺点
要求较高的抽象性,如果叶子和节点有很多差异性的话,例如很多属性和方法都不一样,不适合使用组合模式,而本案例院校展示,他们很多属性和方法都是共同的,类似一个树形结构
使用场景
-
处理类似树形结构,具备统一行为时(如操作系统目录结构,公司组织架构等)
-
想体现对象的部分-整体层次结构,典型的例如文件、文件夹的管理
(文件系统由文件和目录组成,每个文件里装有内容,而每个目录的内容可以有文件和目录,目录就相当于是由单个对象或组合对象组合而成,如果你想要描述的是这样的数据结构,那么你就可以使用组合模式 )
HashMap源码刨析
Java的集合类 - HashMap 中就使用到了组合模式,下面我们通过一段测试代码来进行源码分析
public class Test {
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("第一章","原理");
HashMap<String, String> map = new HashMap<>();
map.put("第二节","探索");
map.put("第三节","出发");
hashMap.putAll(map);
System.out.println(hashMap);
}
}
源码流程分析
首先我们来看 Map ,这是一个接口,其实他类似组合模式中的 Component,为什么这么说,可以看到,里面的put() 方法,和 putAll() 方法,有接口就有实现,接着来看 ,HashMap 实现了 Map 接口,并且实现了上述方法
* @since 1.2
*/
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
private static final long serialVersionUID = 362498820763181265L;
所以 HashMap 就是一个具体的 Composite,那叶子节点呢?
接着我们来看 HashMap 里面的 Node ,可以看到,Node 是 HashMap 里的一个静态内部类
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;
}
其实 Node 就是我们组合模式中的叶子节点,可以看到,Node里面就不再有 put,putAll 等关键方法了,因为他是叶子节点,是最末端的存放数据的结构了,所以只有一些默认方法
我们再来看 HashMap 中目标方法的实现,putVal 接受一个 K 和 V,然后以 Node 的形式放入 HashMap
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);
大家是否可以大致理解这样一个关系,我们通过类图来进行展示
说明
-
Map 就是一个抽象的构建,即组合模式中的 Component
-
HashMap 是组合模式中的非叶子节点,即 Composite ,用于存储子部件,在 Component 接口中实现子部件的相关操作,比如put() , putAll() 等
-
Node 是组合模式中的叶子节点 leaf,里面没有关键方法的实现,是最末端的存放数据的结构
下节预告
OK,到这里,组合模式的相关内容就结束了,下一节,我们开启外观模式的学习,最后,希望大家在学习的过程中,能够感觉到设计模式的有趣之处,高效而愉快的学习,那我们下期见~