# 组合模式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个属性,一个是课程名称,一个是课程价格:

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

它复写了父类的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

它复写了父类的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个课程:“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

前面提到说当组合模式需要“限制类型时会比较复杂”,这个怎么理解呢?比方说现在需求变了,希望在课程或者课程类型的 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

print() 方法中对 levelfor循环 输出空格即可。

应用层做下调整:

    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

运行结果:

课程主目录
-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

上次更新: 2020-08-21 09:02:51(10 小时前)