寂然解读设计模式 - 组合模式

I walk very slowly, but I never walk backwards 

设计模式 - 组合模式


寂然

大家好,我是寂然,本节课,我们来聊设计模式中的组合模式,老规矩,首先我们先通过一个案例需求来引入

案例演示 - 院校展示

在一个页面中展示出学校的院系组成

一个学校会有多个学院, 一个学院有会多个专业

编写程序,完成需求

解决方案一:一般实现

如果用最容易想到的方式,会做成继承关系,我们定义一个学校,学校下面有各个学院子类,而每个学院有各个专业,来展示页面,这样实际上是站在组织大小的角度来进行分层的,类图如下所示


1607422043138.png


但是大家考虑,学校和学院之间,真的是一个继承关系吗 ? 实际上我们的要求是 :在一个页面中展示出学校的院系组成,一个学校有多个学院,一个学院有多个专业, 因此这种方案,不能很好实现的管理的操作,比如对学院专业的添加,删除,遍历等


其实更合适的一种方式是学校包含学院,学院包含专业,应该是一种组合关系,我们可以换一种思路, 把学校、学院、专业都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实现管理操作,其实这种思路就是组合模式,下面,我们一起来看下组合模式的基本介绍

基本介绍

组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以表示 “整体-部分” 的层次关系


组合模式依据树形结构来组合对象,用来表示部分以及整体层次

组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象

原理类图

下面,我们一起来看下组合模式的原理类图,并梳理下组合模式中的角色


1607501343047.png


组合模式角色
  • Component:这是组合模式中的对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理 Component子部件,Component 可以是抽象类或者接口

  • Leaf : 在组合中表示叶子节点,叶子节点没有子节点 ,是最末端的存放数据的结构

  • Composite:非叶子节点, 用于存储子部件,在 Component 接口中实现子部件的相关操作,比如增加(add),删除 (remove)

组合模式解决的问题

组合模式解决这样的场景,当我们的要处理的对象可以生成一颗树形结构,而我们要对树上的节点和叶子进行操作时,组合模式能够提供一致的方式,而不用考虑它是节点还是叶子


1607501911918.png


解决方案二:组合模式
类图展示

下面,我们使用组合模式解决院校展示问题,首先,我们先来画原理类图来分析思路


1607503491842.png


代码演示
/**
 * @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 等关键方法了,因为他是叶子节点,是最末端的存放数据的结构了,所以只有一些默认方法


1607937449147.png


我们再来看 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);

大家是否可以大致理解这样一个关系,我们通过类图来进行展示


1607939066326.png


说明
  • Map 就是一个抽象的构建,即组合模式中的 Component

  • HashMap 是组合模式中的非叶子节点,即 Composite ,用于存储子部件,在 Component 接口中实现子部件的相关操作,比如put() , putAll() 等

  • Node 是组合模式中的叶子节点 leaf,里面没有关键方法的实现,是最末端的存放数据的结构

下节预告

OK,到这里,组合模式的相关内容就结束了,下一节,我们开启外观模式的学习,最后,希望大家在学习的过程中,能够感觉到设计模式的有趣之处,高效而愉快的学习,那我们下期见~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值