结构式:组合模式

1 基本介绍

​ 组合模式为:组合多个对象形成树形结构以表示具有”整体——部分“关系的层次结构。

​ 组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。

​ 在组合模式中引入了抽象构件类Component,它是所有容器类和叶子类的公共父类,客户端针对Component进行编程。

image-20220315160507105

​ 该模式的关键在于定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。

​ 同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构

2 设计分析

2.1 栗子

​ 杀毒软件框架设计,客户可以一致地对待文件和文件夹

image-20220315160838041

代码大概如下

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package DesignPatterns.JavaDesign.Composite;

import java.util.ArrayList;

public class Test {
public static void main(String[] args) {
//针对抽象构件编程
AbstractFile file1, file2, file3, file4, file5, folder1, folder2, folder3, folder4;
folder1 = new Folder("Sunny的资料");
folder2 = new Folder("图像文件");
folder3 = new Folder("文本文件");
folder4 = new Folder("视频文件");
file1 = new ImageFile("小龙女.jpg");
file2 = new ImageFile("张无忌.gif");
file3 = new TextFile("九阴真经.txt");
file4 = new TextFile("葵花宝典.doc");
file5 = new VideoFile("笑傲江湖.rmvb");
folder2.add(file1);
folder2.add(file2);
folder3.add(file3);
folder3.add(file4);
folder4.add(file5);
folder1.add(folder2);
folder1.add(folder3);
folder1.add(folder4);
//从“Sunny的资料”节点开始进行杀毒操作
folder1.killVirus();
}
}

//抽象文件类:抽象构件
abstract class AbstractFile {
public abstract void add(AbstractFile file);

public abstract void remove(AbstractFile file);

public abstract AbstractFile getChild(int i);

public abstract void killVirus();
}

//图像文件类:叶子构件
class ImageFile extends AbstractFile {
private String name;

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

public void add(AbstractFile file) {
System.out.println("对不起,不支持该方法!");
}

public void remove(AbstractFile file) {
System.out.println("对不起,不支持该方法!");
}

public AbstractFile getChild(int i) {
System.out.println("对不起,不支持该方法!");
return null;
}

public void killVirus() {
//模拟杀毒
System.out.println("----对图像文件'" + name + "'进行杀毒");
}
}

//文本文件类:叶子构件
class TextFile extends AbstractFile {
private String name;

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

public void add(AbstractFile file) {
System.out.println("对不起,不支持该方法!");
}

public void remove(AbstractFile file) {
System.out.println("对不起,不支持该方法!");
}

public AbstractFile getChild(int i) {
System.out.println("对不起,不支持该方法!");
return null;
}

public void killVirus() { //模拟杀毒
System.out.println("----对文本文件'" + name + "'进行杀毒");
}
}

//视频文件类:叶子构件
class VideoFile extends AbstractFile {
private String name;

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

public void add(AbstractFile file) {
System.out.println("对不起,不支持该方法!");
}

public void remove(AbstractFile file) {
System.out.println("对不起,不支持该方法!");
}

public AbstractFile getChild(int i) {
System.out.println("对不起,不支持该方法!");
return null;
}

public void killVirus() {
//模拟杀毒
System.out.println("----对视频文件'" + name + "'进行杀毒");
}
}

//文件夹类:容器构件
class Folder extends AbstractFile {
//定义集合fileList,用于存储AbstractFile类型的成员
private ArrayList<AbstractFile> fileList = new ArrayList<AbstractFile>();
private String name;

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

public void add(AbstractFile file) {
fileList.add(file);
}

public void remove(AbstractFile file) {
fileList.remove(file);
}

public AbstractFile getChild(int i) {
return (AbstractFile) fileList.get(i);
}

public void killVirus() {
System.out.println("****对文件夹'" + name + "'进行杀毒");
//模拟杀毒
// 递归调用成员构件的killVirus()方法
for (Object obj : fileList) {
((AbstractFile) obj).killVirus();
}
}
}

2.2 原来方案的问题

​ 由于在AbstractFile中声明了大量用于管理和访问成员构件的方法,例如add()、remove()等方法,我们不得不在新增的文件类中实现这些方法,提供对应的错误提示和异常处理

​ 解决方案1:将叶子构件的add()、remove()等方法的实现代码移至AbstractFile类中,由AbstractFile提供统一的默认实现

​ 解决方案2:除此之外,还有一种解决方法是在抽象构件AbstractFile中不声明任何用于访问和管理成员构件的方法

​ 根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式

2.3 透明组合模式

​ 透明组合模式中,抽象构件Component中声明了所有用于管理成员对象的方法,包括add()、remove()以及getChild()等方法,这样做的好处是确保所有的构件类都有相同的接口。

image-20220315164205913

​ 透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的。叶子对不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供add()、remove()以及getChild()等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)。

2.4 安全组合模式

​ 安全组合模式中,在抽象构件Component中没有声明任何用于管理成员对象的方法,而是在Composite类中声明并实现这些方法

image-20220315164253322

​ 安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。在实际应用中,安全组合模式的使用频率也非常高,在Java AWT中使用的组合模式就是安全组合模式。

3 总结

优缺点分析

优点

  1. 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制
  2. 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
  3. 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”
  4. 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

缺点

​ 在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂

适用场景

  1. 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们
  2. 在一个使用面向对象语言开发的系统中需要处理一个树形结构
  3. 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。

结构式:组合模式
https://2w1nd.github.io/2022/03/15/设计模式/结构式:组合模式/
作者
w1nd
发布于
2022年3月15日
许可协议