# 组合模式Composite
阅读量  loading 
 # 一、概念
# 1、定义
将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使客户端对单个对象和组合对象保持一致的方式处理
# 2、类型
结构型
# 3、适用场景
- 希望客户端可以忽略组合对象与单个对象的差异时
 - 处理一个树形结构时
 
# 4、优点
- 清楚地定义分层次的复杂对象,表示对象的全部或部分层次
 - 让客户端忽略了层次的差异,方便对整个层次结构进行控制
 - 简化客户端代码
 - 符合开闭原则
 
# 5、缺点
- 当需要限制类型时会比较复杂
 - 使设计变得更加抽象
 
# 6、相关设计模式
- 组合模式和访问者模式
 
可以使用访问者模式访问组合模式中的递归结构
# 二、Coding
看这样子的场景,现在各大平台都有视频教学,每个视频都有自己的所属的课程分类,我们先来创建一个课程和课程分类组件的父类:
public abstract class CatalogComponent {
    public void add(CatalogComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持添加操作");
    }
    public void remove(CatalogComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持删除操作");
    }
    public String getName(CatalogComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持获取名称操作");
    }
    public double getPrice(CatalogComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持获取价格操作");
    }
    public void print() {
        throw new UnsupportedOperationException("不支持打印操作");
    }
}
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
这里声明成了一个抽象类,并且每个方法中都抛出异常,意思是让继承它的类自己确定复写哪些方法。
接下来是课程的实现类,它有2个属性,一个是课程名称,一个是课程价格:
public class Course extends CatalogComponent {
    private String name;
    private double price;
    public Course(String name, double price) {
        this.name = name;
        this.price = price;
    }
    @Override
    public String getName(CatalogComponent catalogComponent) {
        return name;
    }
    @Override
    public double getPrice(CatalogComponent catalogComponent) {
        return price;
    }
    @Override
    public void print() {
        System.out.println("Course name: " + name + ", price: " + price);
    }
}
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
它复写了父类的3个方法,支持获取名称、获取价格、和输出的方法。
然后是课程分类的实现类,它也有2个属性,一个是所属分类下的课程,一个是分类名称。
public class CourseCatalog extends CatalogComponent {
    private List<CatalogComponent> itemList = new ArrayList<>();
    private String name;
    public CourseCatalog(String name) {
        this.name = name;
    }
    @Override
    public String getName(CatalogComponent catalogComponent) {
        return name;
    }
    @Override
    public void add(CatalogComponent catalogComponent) {
        itemList.add(catalogComponent);
    }
    @Override
    public void remove(CatalogComponent catalogComponent) {
        itemList.remove(catalogComponent);
    }
    @Override
    public void print() {
        System.out.println(name);
        for (CatalogComponent catalogComponent : itemList) {
            System.out.print("-");
            catalogComponent.print();
        }
    }
}
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
它复写了父类的4个方法,获取名称、添加课程、移除课程和打印。
看看应用层怎么调用吧:
    public static void main(String[] args) {
        CatalogComponent linuxCourse = new Course("Linux课程", 11);
        CatalogComponent windowsCourse = new Course("WindowsCourse课程", 11);
        CatalogComponent javaCourseCatalog = new CourseCatalog("Java课程目录");
        CatalogComponent mallCourse1 = new Course("Java电商一期", 55);
        CatalogComponent mallCourse2 = new Course("Java电商二期", 66);
        CatalogComponent designPattern = new Course("Java设计模式", 77);
        javaCourseCatalog.add(mallCourse1);
        javaCourseCatalog.add(mallCourse2);
        javaCourseCatalog.add(designPattern);
        CatalogComponent mainCourseCatalog = new CourseCatalog("课程主目录");
        mainCourseCatalog.add(linuxCourse);
        mainCourseCatalog.add(windowsCourse);
        mainCourseCatalog.add(javaCourseCatalog);
        mainCourseCatalog.print();
    }
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
首先创建了2个课程:“Linux课程”和“WindowsCourse课程”,然后创建了一个“Java课程目录”,给这个目录中添加了3个子课程,最终将所有课程和课程目录统一放入了一个“课程主目录中”进行打印。
运行结果:
课程主目录
-Course name: Linux课程, price: 11.0
-Course name: WindowsCourse课程, price: 11.0
-Java课程目录
-Course name: Java电商一期, price: 55.0
-Course name: Java电商二期, price: 66.0
-Course name: Java设计模式, price: 77.0
 1
2
3
4
5
6
7
2
3
4
5
6
7
前面提到说当组合模式需要“限制类型时会比较复杂”,这个怎么理解呢?比方说现在需求变了,希望在课程或者课程类型的 print() 方法中打印的时候能通过不同个数的 - 体现出课程的层级结构来,这时候会需要动态的判断当前类型是课程还是课程类型。
修改课程分类的实现类,添加 level 属性,修改后的代码:
public class CourseCatalog extends CatalogComponent {
    private List<CatalogComponent> itemList = new ArrayList<>();
    private String name;
    private Integer level;
    public CourseCatalog(String name, Integer level) {
        this.name = name;
        this.level = level;
    }
    @Override
    public String getName(CatalogComponent catalogComponent) {
        return name;
    }
    @Override
    public void add(CatalogComponent catalogComponent) {
        itemList.add(catalogComponent);
    }
    @Override
    public void remove(CatalogComponent catalogComponent) {
        itemList.remove(catalogComponent);
    }
    @Override
    public void print() {
        System.out.println(name);
        for (CatalogComponent catalogComponent : itemList) {
            if (level != null) {
                for (int i = 0; i < level; i++) {
                    System.out.print("-");
                }
            }
            catalogComponent.print();
        }
    }
}
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
在 print() 方法中对 level 做 for循环 输出空格即可。
应用层做下调整:
    public static void main(String[] args) {
        CatalogComponent linuxCourse = new Course("Linux课程", 11);
        CatalogComponent windowsCourse = new Course("WindowsCourse课程", 11);
        CatalogComponent javaCourseCatalog = new CourseCatalog("Java课程目录", 2);
        CatalogComponent mallCourse1 = new Course("Java电商一期", 55);
        CatalogComponent mallCourse2 = new Course("Java电商二期", 66);
        CatalogComponent designPattern = new Course("Java设计模式", 77);
        javaCourseCatalog.add(mallCourse1);
        javaCourseCatalog.add(mallCourse2);
        javaCourseCatalog.add(designPattern);
        CatalogComponent mainCourseCatalog = new CourseCatalog("课程主目录", 1);
        mainCourseCatalog.add(linuxCourse);
        mainCourseCatalog.add(windowsCourse);
        mainCourseCatalog.add(javaCourseCatalog);
        mainCourseCatalog.print();
    }
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
运行结果:
课程主目录
-Course name: Linux课程, price: 11.0
-Course name: WindowsCourse课程, price: 11.0
-Java课程目录
--Course name: Java电商一期, price: 55.0
--Course name: Java电商二期, price: 66.0
--Course name: Java设计模式, price: 77.0
 1
2
3
4
5
6
7
2
3
4
5
6
7