组合模式 Composite Pattern 概述
- 什么是组合模式: 部分与整体的一种层次结构型设计模式,设计单个对象与组合对象使用一致的处理方式,然客户以一致的方式处理个别对象与组合对象
- 通过实际案例解释组合模式: 例如目录,在目录下面可能还有子目录,在子目录下方可能还有文件,例如学校: 学校下方有各个院校,院校下方有分为各个系,系下面可能还有多个种类,关注点:树形结构,大的级别包含小的级别
- 通过目录来说明可能存在的问题: 上级目录中删除或添加子级目录,子集目录中删除或添加文件,文件中可能还有属于文件的目录,不确定哪个是子节点,并且级别不同,添加删除的是当前级别中的子级别,其它级别中的添加删除与当前级别中的类型不同,通过组合设计模式,做到一视同仁,强调的是客户端忽略层次结构的差异,方便对整个层次的控制符合开闭原则,更方便的添加新的父节点,子节点,遍历组织结构,或处理的树形接口,容易添加节点或叶子节点,如果节点与叶子节点差异性较大不适合使用
组合模式示例
案例: 学习院系展示系统,可以展示学校中的学院,学院中的各个系,输出学校中的院校,院校中的各个系,以及学校对学院的增删查,学院对系的曾删查,系对自己的描述
实现思路
- 如何做到客户端忽略各个级别? 根据实现功能抽象出公共的抽象父类,各个级别类型共同实现该接口,在客户端调用时只需要创建父类引用
- 如何做不同级别中功能的兼容? 在抽象父类中先定义抽象方法,或默认的方法,可以是抛出异常,可以是空方法,然后根据不同级别要实现的功能,对方法进行重写
- 不同级别是包含关系,例如学习中包含多个学院,学院中又包含多个学系
代码示例
- 根据功能抽象出公共的父类Management,添加,删除,描述等,提供抽象方法,默认方法等,子类学校,院,系继承父类,根据自己的需求选在重写这些方法,由于"学校",“学院”,"系"等都实现了Management父类,在客户端调用时,不需要考虑调级别类型,以相同方式进行处理即可
//根据功能抽象出公共父类
abstract class Management{
public String nameDesc;
//提供带参构造对名称,人数进行赋值
public Management(String nameDesc){
this.nameDesc = nameDesc;
}
//功能方法: 根据级别需求的不同进行重写
//添加操作
//提供抛出异常的默认方法,适用于不管什么级别都需要实现的功能
//但是这个功能对应级别的不同实现的逻辑不同
public void add(Management management){
throw new UnsupportedOperationException("操作有误");
}
//删除操作
//提供空的默认方法,适用于有的级别中可能没有这个功能,
public void delete(String nameDesc){
}
//描述操作
//抽象方法
abstract void desc();
//获取总人数
abstract int getPersonCount();
}
- 创建不同级别的实现子类,例如 系—>学院—>院校,注意:级别不同功能不同,例如学院中可以添加删除系,学校中可以添加删除学院,不同级别之间相互的关系,大的包含小的,
系,假设系是最小节点,不可以添加删除,只可以获取,则不需要重写父类中提供的添加删除方法,调用父类中默认的
院,注意院中可以包含多个系,将系以集合组合到院中,院中可以添加删除系,重写父类的添加删除方法…
//系
class Series extends Management{
//系中添加人数属性
public int personCount;
public Series(String nameDesc, int personCount){
super(nameDesc);
this.personCount = personCount;
}
//重写描述方法,获取当前系的名称,人数即可
@Override
public void desc() {
System.out.println(nameDesc+",人数"+personCount);
}
//重写获取人数方法,获取当前系的人数
@Override
public int getPersonCount() {
return personCount;
}
}
//学院
class College extends Management{
//学院中包含多个系,注入系集合
public List<Management> seriesList;
public College(String nameDesc) {
super(nameDesc);
}
//重写院校的获取人数的方法,该方法中是通过
//遍历组合进来的系,获取每个系中的人数相加
@Override
public int getPersonCount() {
if(null == seriesList || seriesList.size() <= 0)
return 0;
return seriesList.stream()
.map(Management::getPersonCount)
.reduce(Integer :: sum).get();
}
//重写院校的描述方法,首先获取院校的基本信息进行描述
//然后遍历院校中组合进来的系,通过系对象调用各自系的desc方法
@Override
public void desc() {
System.out.println(nameDesc+",该院总人数为:"+getPersonCount());
if(null == seriesList || seriesList.size <= 0)
return;
seriesList.stream().forEach(management -> management.desc());
}
//重写添加方法,院校中可以添加系
@Override
public void add(Management series){
if(null == seriesList){
seriesList = new ArrayList<>();
seriesList.add(series);
}else {
seriesList.add(series);
}
}
//删除方法
@Override
public void delete(String nameDesc){
if(null == seriesList || seriesList.size() <= 0)
return ;
//获取seriesList中不包含该nameDesc的重新赋值给seriesList
seriesList = seriesList.stream()
.filter(s -> !s.nameDesc.equals(nameDesc))
.collect(Collectors.toList());
}
}
//学校
class School extends Management{
//学校中包含学院
public List<Management> collegeList;
public School(String nameDesc) {
super(nameDesc);
}
@Override
public void desc() {
//本级别中学校的描述
System.out.println(nameDesc+",该校总人数为:"+getPersonCount());
if(null == collegeList)
return;
//循遍历学校中包含的College院校,调用College中的描述,而College中的desc()方法内部
//是遍历系院校中包含的系Series,通过Series调用系的desc()方法
collegeList.stream().forEach(college -> college.desc());
}
@Override
public int getPersonCount() {
if(null == collegeList || collegeList.size() <= 0)
return 0;
return collegeList.stream()
.map(Management::getPersonCount)
.reduce(Integer :: sum).get();
}
@Override
public void add(Management college){
if (null == collegeList) {
collegeList = new ArrayList<>();
collegeList.add(college);
}else {
collegeList.add(college);
}
}
@Override
public void delete(String nameDesc){
if(null == collegeList || collegeList.size() <= 0)
return ;
//删除学校中的系,获取集合中不包含该nameDesc的重新赋值给collegeList
collegeList = collegeList.stream()
.filter(c -> !c.nameDesc.equals(nameDesc))
.collect(Collectors.toList());
}
}
- 调用测试,客户端在添加删除时,并不会根据级别不同调用不同的方法,而是一致的操作
public class Test3 {
public static void main(String[] args) {
//通过new Series()可以看出创建的是系
Management series1 = new Series("通讯工程系",400);
//调用自己重写的desc方法
series1.desc();
//由于系是最小节点,不可以添加和删除,没有重写添加删除方法,实际调用的是父类Management中的
//series1.delete("通讯工程系");
//创建"网络安全系"
Management series2 = new Series("网络安全系", 200);
//创建"计算机学院"
Management college = new College("计算机学院");
//将前面创建的系通过add()方法添加到学院中
college.add(series1);
college.add(series2);
//college.desc();
//创建"离离原上草学校"
Management school = new School("离离原上草学校");
//将学院添加到学校中
school.add(college);
//删除学校中的"计算机学院"
school.delete("计算机学院");
school.desc();
}
}
JDK 中组合模式的应用案例
查看集合包下的 Map接口,在接口中有抽象的公共方法,该接口被被AbstractMap抽象类实现,拿接口中的putAll()方法来说, 在Map中定义了putAll()抽象方法, Abstract抽象子类中重写了该方法,但是并不是实际的功能处理,而是抛出了一个异常,再继续向下查找,HashMap继承了,Map接口,并实现了AbstractMap抽象子类,重写了putAll()抽象方法,进行实际处理,在这里HashMap就相当于我们现在各个级别的的具体节点例如学校,院等,继续向下分析,在HashMap中有一个Node内部类,这个Node就可以看为是叶子节点,相当于此处的系,而Map,AbstractMap相当于此处根据功能抽象出的公共父类Management,不同之处在于Map,多抽象出了一个接口