组合模式

定义:组合多个对象形成树的结构以表示“整体——部分”关系,对单个对象和组合对象进行无差别对待。组合模式是一种结构性模式

场景:文件夹中包含图片、文本文件和文件夹

这个场景中包含三个对象:图片、文件夹、文本文件,其中图片、文本文件和文件夹是聚合关系,于是我们产出了如下代码

public class Folder {

    private List<ImageFile> imageFiles = new ArrayList<>();
    private List<TextFile> textFiles = new ArrayList<>();
    private List<Folder> folders = new ArrayList<>();

    private String name;

    public Folder(String name) {
        this.name = name;
    }

    public void addImageFile(ImageFile imageFile) {
        imageFiles.add(imageFile);
    }

    public void addTextFiles(TextFile textFile) {
        textFiles.add(textFile);
    }

    public void addFolder(Folder folder) {
        folders.add(folder);
    }

}

public class ImageFile {
    private String name;

    public ImageFile(String name) {
        this.name = name;
    }

}

public class TextFile {

    private String name;

    public TextFile(String name) {
        this.name = name;
    }
}
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
40
41
42
43

这个时候,我们对场景进行改变:现在我们增加一个文件种类——影音文件,按照上述代码逻辑会增加一个影音类,并且Folder类也需要做修改

public class Folder {

    private List<ImageFile> imageFiles = new ArrayList<>();
    private List<TextFile> textFiles = new ArrayList<>();
    private List<Folder> folders = new ArrayList<>();
    //新增集合保存影音文件
    private List<TVFile> tvFiles = new ArrayList<>();

    private String name;

    public Folder(String name) {
        this.name = name;
    }

    public void addImageFile(ImageFile imageFile) {
        imageFiles.add(imageFile);
    }

    public void addTextFiles(TextFile textFile) {
        textFiles.add(textFile);
    }

    public void addFolder(Folder folder) {
        folders.add(folder);
    }
	//新增添加影音文件方法
    public void addTVFile(TVFile tvFile) {
        tvFiles.add(tvFile);
    }

}

public class TVFile {
    private String name;

    public TVFile(String name) {
        this.name = name;
    }
    
}
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
40

我们在增加一个文件种类的时候对原有的代码进行了更改,违反了开闭原则。那么我们怎么做才能保证开闭原则呢,也就是不对Folder类进行修改呢,最常见的方法就是根据依赖倒置原则进行修改,即使程序面向接口编程。于是乎我们新增构件接口Component,并使得上述类都实现该接口,并修改相应程序

public interface Component {
}

public class Folder implements Component {

    private List<Component> files = new ArrayList<>();

    private String name;

    public Folder(String name) {
        this.name = name;
    }

    public void addFile(Component file) {
        files.add(file);
    }

}

public class ImageFile implements Component {
    
    private String name;

    public ImageFile(String name) {
        this.name = name;
    }

}

public class TextFile implements Component  {

    private String name;

    public TextFile(String name) {
        this.name = name;
    }
    
}
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

接下来我们再添加一个文件种类时就只需要增加一个实现了Component接口的类就可以了,符合开闭原则和依赖倒置原则。

文件夹应该具有添加、移除、展示文件的方法,而文件只需要拥有展示的方法就可以了,那么我们是否应该在接口中将添加、删除方法添加进来呢?答案是都可以,而且对于不同的处理方法,组合模式又可以分为透明组合模式和安全组合模式。

安全组合模式如下

public interface Component {
    void show();
}

public class Folder implements Component {

    private List<Component> files = new ArrayList<>();

    private String name;

    public Folder(String name) {
        this.name = name;
    }

    public void addFile(Component file) {
        files.add(file);
    }

    public void removeFile(Component file) {
        files.remove(file);
    }

    public void show() {
        for(Component component : files) {
            System.out.println(component);
        }
    }

}

public class ImageFile implements Component {
    private String name;

    public ImageFile(String name) {
        this.name = name;
    }

    public void show() {
        System.out.println(this);
    }

}

public class TextFile implements Component {

    private String name;

    public TextFile(String name) {
        this.name = name;
    }

    public void show() {
        System.out.println(this);
    }

}
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

透明组合模式如下

public interface Component {
    void show();
    void addFile(Component component);
    void removeFile(Component component);
}

public class Folder implements Component {

    private List<Component> files = new ArrayList<>();

    private String name;

    public Folder(String name) {
        this.name = name;
    }

    @Override
    public void addFile(Component file) {
        files.add(file);
    }

    @Override
    public void removeFile(Component file) {
        files.remove(file);
    }

    @Override
    public void show() {
        for(Component component : files) {
            System.out.println(component);
        }
    }

}

public class ImageFile implements Component {
    private String name;

    public ImageFile(String name) {
        this.name = name;
    }

    public void show() {
        System.out.println(this);
    }

    @Override
    public void addFile(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void removeFile(Component component) {
        throw new UnsupportedOperationException();
    }

}

public class TextFile implements Component {

    private String name;

    public TextFile(String name) {
        this.name = name;
    }

    public void show() {
        System.out.println(this);
    }

    @Override
    public void addFile(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void removeFile(Component component) {
        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
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

可以看到,在透明组合模式中,Component构件规定了所有的接口,而在叶子构件中,对于无法实现的方法抛出了不支持异常;而在安全组合模式中,Component只规定通用接口

总结

在组合模式中,一共存在三类角色

  • Component:组合部件,为要组合的对象提供统一的接口,在上述程序中对应Component接口
  • Leaf:叶子,标识叶子结点,叶子结点不能有子节点,在上述代码中对应TextFile和ImageFile
  • Composite:枝干节点,可以存储部件,在上述代码中对应Folder

类图如下

由上述程序的重构过程可以发现,对于需要表示“整体——部分”关系的对象关系而言,组合模式提供了近乎于完美的解决方案

  • 通过对Component接口的实现,在新增一个构件时无需对原有的程序进行修改,符合开闭原则

  • 通过叶子对象和容器对象的组合,可以形成复杂的树形结构,但是对树形结构的控制又很简单

  • 客户端对叶子对象和容器对象可以进行统一的处理而不需要关心处理的是叶子还是容器