首先我们来创建一个组合模式的包,新建一个包名,composite,那我们现在引入一个业务场景,我们就拿一个网站来说,
这里面有很多课程,也有课程目录,那课程有名称与价格,例如说JAVA的课程,那JAVA的课程分属于JAVA课程这个目录下,
JAVA课程这个目录,有很多JAVA课程,Python课程目录有很多Python课程,如果我们使课程的目录和课程呢,继承同一个
抽象类,例如目录组件,那就可以把课程本身,还有由课程组合成的目录呢,视为同一类对象,进行操作,但是在操作上,
还会有一些差别,具体的差别定制化处理,例如说打印这个操作,我们可以打印一个课程的名称和价格,我们也可以打印
这个课程目录,如果我们打印这个课程目录,就会把这个课程目录下边所有的课程打印出来,对于这个操作我们可以认为,
是一类操作,对他们两是想通的,那我们首先创建一个抽象的上层组件,目录组件,CatalogComponent
看看组合模式的树状结构,我们把课程和课程目录全部都继承目录组件,对于print方法他们两可以一致的处理,
对于其他不一样的方法呢,在这个抽象父类上,会有默认的实现,例如说抛出异常,所以当Course去调用add和remove的时候,
就会抛出异常,同时课程目录去getPrice的时候,也会抛出异常,因为这个目录本身他没有价格,当然我们也可以重写,比如
这个目录一旦获取价格的话,就代表获取这个目录下边所有课程价格的和,这样也是可以的,还是看我们具体的业务场景,
那在我们这里边为了区分度,getPrice我们就认为这个目录本身是没有价值的,例如你们在学习网上也不可能花钱,去买一个
课程目录来,那对于name课程目录是可以重写的,那我们现在回到这个类里面,getName已经加入进来了
而这两个的关系可以看出来,是一个组合关系,这个菱形的箭头,可以看出来这里有一个星号,那这个星就是代表多的意思,
例如说items,这个是一个集合,那UML非常清晰,现在来写一下,测试类,直接创建一个Test
package com.learn.design.pattern.structural.composite;
/**
* CatalogComponent这个类是抽象类
* 对于这个抽象类里边是有默认实现的
* 交给子类来决定
* 是否需要重写他
* 因为课程目录和课程本身
* 这两个层次的对象
* 操作并不完全一样
* 但是呢又有一样的
* 所以我们直接在父类上把这些方法的实现直接都写完
* 然后交由子类选择性的重写
* 那这个抽象组件就写好了
* 接下来我们会使课程和课程目录
* 这两个类来继承这个抽象类
* 现在我们来创建一个课程类
*
*
* @author Leon.Sun
*
*/
public abstract class CatalogComponent {
/**
* 那我们写一个add
* add操作呢肯定是add自己本身
* 然后我们交由课程和课程目录来继承这个抽象类
* 同理我们再增加一些其他的
*
*
* @param catalogComponent
*/
public void add(CatalogComponent catalogComponent){
/**
* 这里直接写上不支持添加操作
*
*
*/
throw new UnsupportedOperationException("不支持添加操作");
}
/**
* 从第二个开始remove
*
*
* @param catalogComponent
*/
public void remove(CatalogComponent catalogComponent){
/**
* 不支持删除操作
*
*
*/
throw new UnsupportedOperationException("不支持删除操作");
}
/**
* 第三个就是获取名称
* 返回值就是String类型
*
*
* @param catalogComponent
* @return
*/
public String getName(CatalogComponent catalogComponent){
/**
* 不支持获取名称操作
*
*
*/
throw new UnsupportedOperationException("不支持获取名称操作");
}
/**
* 这个方法需要改造成getPrice
* 获取具体的价格
* 返回值是double
*
*
* @param catalogComponent
* @return
*/
public double getPrice(CatalogComponent catalogComponent){
/**
* 不支持获取价格操作
*
*
*/
throw new UnsupportedOperationException("不支持获取价格操作");
}
/**
* 下边就是打印
* 打印很好理解
* 如果组件是课程
* 那就打印课程
* 如果组件是课程目录
* 那就打印这个课程目录下面的课程
*
* 然后来到最上层的抽象类
* 也把里面的入参去掉
* 这样就可以了
*
*
*/
public void print(){
/**
* 不支持打印操作
*
*
*/
throw new UnsupportedOperationException("不支持打印操作");
}
}
package com.learn.design.pattern.structural.composite;
/**
* 我们使他继承CatalogComponent
* 这个课程的类就写好了
* 然后我们再写一个课程的目录类
* 我们再创建一个类
* CourseCatalog
*
*
* @author Leon.Sun
*
*/
public class Course extends CatalogComponent {
/**
* 他有两个属性
* 课程的名字
*
*/
private String name;
/**
* 还有课程的价格
* 这里面就用小double了
* 我们主要关注组合模式
*
*
*/
private double price;
/**
* 我们把构造器写一下
*
*
* @param name
* @param price
*/
public Course(String name, double price) {
this.name = name;
this.price = price;
}
/**
* 还有我们要重写抽象类的方法
* 重写哪几个方法呢
* 首先对于add
* add是目录类的方法
* add只有目录可以add
* 课程不能在他的子节点再添加课程了
* 但是课程目录可以
* remove也是同理
* 课程目录可以保存
* 可以remove一个课程
* 也可以add一个课程
* 但是课程本身可以获取课程的名字
* 课程的报价
* 还有打印具体的课程
* 所以重写这三个方法
*
*
*/
@Override
public String getName(CatalogComponent catalogComponent) {
/**
* 这里的实现我们也要改一下
* return什么呢
* this.name
*
*
*/
return this.name;
}
@Override
public double getPrice(CatalogComponent catalogComponent) {
/**
* 价格也是一样
* this.price
*
*
*/
return this.price;
}
/**
* 课程里的入参也不需要
* 因为打印的时候
* 打印的name和price呢
* 也是课程本身的
*
*
*/
@Override
public void print() {
/**
* print这里面我们也重写一下
*
*
*/
System.out.println("Course Name:"+name+" Price:"+price);
}
}
package com.learn.design.pattern.structural.composite;
import java.util.ArrayList;
import java.util.List;
/**
* 他同样的继承CatalogComponent
*
*
* @author Leon.Sun
*
*/
public class CourseCatalog extends CatalogComponent {
/**
* 在目录里面肯定有很多的课程
* 而这个课程又是目录组件
* 所以我们可以在这里面声明一个
* 目录组件是个抽象类
*
*
*/
private List<CatalogComponent> items = new ArrayList<CatalogComponent>();
/**
* 目录本身还有name
* 比如说这个是JAVA课程的目录
* 那这个就是他的名称
*
*
*/
private String name;
/**
* 增加这么一个属性
*
*
*/
private Integer level;
/**
* 然后我们写一下他的构造器
*
* 然后把level传进来
*
*
*
* @param name
* @param level
*/
public CourseCatalog(String name,Integer level) {
this.name = name;
/**
* 然后在这里面赋值
* 为什么要用Integer呢
* 因为如果用int的话
* 那么他会有默认值的
* 所以我们使用Integer
* 然后进行空判断
* 往下走
*
*
*/
this.level = level;
}
/**
* 现在我们可以继续重写它的方法
* 对于目录来说add和remove
* 都是有的
* 同时它还可以打印
* 所以我们重写这三个方法
* 那add的实现也很简单
* 直接用 items.add
* 把catalogComponent这个放进来
* 就可以了
*
*
*/
@Override
public void add(CatalogComponent catalogComponent) {
items.add(catalogComponent);
}
/**
* 我们把getName写一下
* 这里面也很简答
* return this.name;
* 就可以了
* 来到UML
* getName已经加入进来了
*
*/
@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}
/**
* remove也是同理
* 直接items.remove
* 把catalogComponent这个对象放进来
*
*
*/
@Override
public void remove(CatalogComponent catalogComponent) {
items.remove(catalogComponent);
}
/**
* 在打印的时候呢
* 我们要遍历这个目录
* 而这个目录就是他本身
* 所以这个print方法在封装的时候
* 他并不需要入参
* 所以这里我们可以干掉
* 这个是通过一个业务场景来分析
* 这个方法的入参的
* 那我们来到Course里边
*
* 所以根据不同的业务场景
* 我们如果封装组合模式
* 具体的入参
* 还有返回值
* 到底是什么类型
* 有没有这个参数等等
* 都要根据实际的业务来确定
* 那print也非常的简单
* 我们直接一个for循环
* 就可以了
*
* 来到这里边
*
*
*
*/
@Override
public void print() {
/**
* 直接输出一个this.name
* 课程目录
*
*
*/
System.out.println(this.name);
/**
* 循环items
*
*
*/
for(CatalogComponent catalogComponent : items){
/**
* 然后我们在输出课程的时候
* 我们再增加一个空格
*
* 如果this.level不等于空的话
* 那这里面就调用一下for循环
* 来拼接这个空格
*
* 为什么用this呢
* 我们也可以通过instanceof这个关键字
* 来判断这个类型
* 是目录类型还是课程类型
* 如果是目录类型
* 再打印级别的空格个数
* 那样也是可以的
* 为什么这里使用this呢
* 因为使用this的时候呢
* 肯定是课程目录本身来执行
* 那我们现在回到Test里边
*
*
*/
if(this.level != null){
for(int i = 0; i < this.level; i++){
/**
* 也就是我们在打印目录的时候
* 里面多了一些空格
* 这样在排版的时候
* 就很容易看清楚
* 他们的从属关系
* 我们回到Test里边
*
* 在这里面再打印空格就OK了
*
*
*/
System.out.print(" ");
}
}
/**
* 直接调用catalogComponent.print
* 这样这个方法的实现就写好了
* 接下来我们来看一下他的UML图
* 这个位置刚刚好
*
*
*/
catalogComponent.print();
}
}
}
package com.learn.design.pattern.structural.composite;
/**
*
* @author Leon.Sun
*
*/
public class Test {
/**
* 直接写一个主函数
* 现在就把课程目录和课程
* 都认为是CatalogComponent
* 就可以了
* 现在我们把课程和课程目录
* 都认为是CatalogComponent
* 他们两都是这个类型
* 进行统一处理
*
*
* @param args
*/
public static void main(String[] args) {
/**
* 首先我们创建一个CatalogComponent
* linuxCourse
* new一个课程
* 名字"Linux课程"
* 价格呢是11
*
*
*/
CatalogComponent linuxCourse = new Course("Linux课程",11);
/**
* 再new一个
* windows课程
* 操作系统课程
* 那对于操作系统的课程
* 我们学习网的目录
* 创建好了
*
*
*
*/
CatalogComponent windowsCourse = new Course("Windows课程",11);
/**
* 然后我们再创建一个JAVA课程的目录
* 这回我们要创建目录了
* javaCourseCatalog
* new一个Course目录
* 这里面放课程名称
* "Java课程目录"
* 现在就把我们的课程
* 填进来
*
* JAVA课程是二级目录
* 这个时候我们来run一下
* 看一下结果
* 结果已经出来了
* 我们来看一下
* 现在就非常清晰了
* 主目录是学习网课程主目录
* 然后下一层有三个课程
* 分别是linux windows
* 还有一个课程是课程目录
* 然后在JAVA课程目录里面又有三个
* JAVA课程
* 这样通过这个排版
* 他们之间的父子关系
* 就非常清晰了
* 那组合模式coding就完成了
* 组合模式使用的时候坑非常多
* 我们把坑都埋到了coding实战当中
* 如果我们非常顺利的编写下来的话
* 你们对于组合模式
* 应用可能不会太深
* 通过这么一个踩坑的方式
* 目的是希望你们在使用组合模式的时候
* 刚刚这些点一定要注意
* 同时希望你们
* 对这一块的理解能够更加深入一些
*
*
*/
CatalogComponent javaCourseCatalog = new CourseCatalog("Java课程目录",2);
/**
* 我们有mmall电商一期
* mmallCourse1
* new一个Course
* 名字是"Java电商一期"
* 价格呢是55
*
*
*/
CatalogComponent mmallCourse1 = new Course("Java电商一期",55);
/**
* 然后再创建一个二期
* mmallCourse2
* 价格是66
*
*
*/
CatalogComponent mmallCourse2 = new Course("Java电商二期",66);
/**
* 再创建一个设计模式
* designPattern
* "Java设计模式"
* 价格是77
*
*
*/
CatalogComponent designPattern = new Course("Java设计模式",77);
/**
* 目录里面添加这三个JAVA课程
* 这三个课程就添加到JAVA课程里面
* 那么我们还可以创建一个目录
*
*
*/
javaCourseCatalog.add(mmallCourse1);
javaCourseCatalog.add(mmallCourse2);
javaCourseCatalog.add(designPattern);
/**
* 这个目录是一个主目录
* 我们就叫imoocMainCourseCatalog
* 学习网的课程主目录
* 我们写一下他的名称
* "学习网课程主目录"
*
* 我们在构造这个目录的时候
* 需要传一个级别了
* 例如这个主目录是一个一级目录
*
*
*
*/
CatalogComponent imoocMainCourseCatalog = new CourseCatalog("学习网课程主目录",1);
/**
* 然后我们向学习网的课程主目录里面添加
* 添加什么呢
* 我们首先添加一个linux课程
*
*/
imoocMainCourseCatalog.add(linuxCourse);
/**
* 再添加一个windows课程
*
*
*/
imoocMainCourseCatalog.add(windowsCourse);
/**
* 再添加一个JAVA课程的目录
* 例如说add这个方法
* 我可以填课程
* 也可以填目录
* 把他两视为一个对象
* 减少这两个对象在使用的时候差异
* 这个就是组合模式的核心
* 然后我们调用一下
*
*
*
*/
imoocMainCourseCatalog.add(javaCourseCatalog);
/**
* 调用一下他的打印方法
* 打印学习网的课程主目录
* 结果已经出来了
* 这个课程下边有linux课程
* 有windows课程
* 还有他的价格
* 三个JAVA课程
* 那现在在输出中已经把它们打平了
* 因为我们在目录里边
* 并没有写上具体的目录名称
* 所以我们现在来加上
* 我们再run一下
* 结果已经出来了
* 我们看一下
* 主目录加了两个空格
* 在主目录下面有两个操作系统的课程
* 然后是JAVA课程目录
* Java课程目录和他两是平级的
* 然后在JAVA课程目录下边
* 又有三个JAVA课程
* 那我们看一下
* 他的缺点是什么呢
* 也就是如果我们要动态的
* 对类型进行限制的话
* 会使这个业务逻辑变得复杂
* 我们现在想在JAVA目录本身是一个二级目录
* 因为学习网主目录是一级目录
* 他呢是二级目录
* 对这个空格打印的时候
* 需要打印两个空格
* 从而在打印上来区分
* 从属关系
* 那在这里就需要进行动态的判断了
* 所以这个坑也是埋了一下
* 为了让你们能体会到
* 组合模式的缺点
* 所以我们这代码还要改
* 我们现在想一下
* 刚才我们都改什么了
* 首先对于组合模式
* 最上层的抽象组件
* 参数和返回值
* 这里要多花心思
* 来确定他
* 还有目录在重写方法的时候
* 来确定哪些组合业务逻辑的重写
* 哪些是不符合业务逻辑的
* 例如目录本身并没有价格
* 那如果我们重写为这个目录获取价格的时候
* 代表获取这个目录下边所有课程的价格
* 那我们还要考虑
* 一旦这个目录下边还有目录的话
* 是不是要做深层次的递归
* 那这些都是我们要考虑的点
* 就例如我们现在碰到的问题
* 我们需要排版更清晰的
* 从属关系
* 那我们还要在目录这个结构下
* 增加一个level
* 这么一个属性
* 那很简单
* 来增加一个属性
*
*
*
*
*/
imoocMainCourseCatalog.print();
}
}