设计模式(10):组合模式COMPOSITE

组合模式

意图

将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。


使用场景

以下情况使用Composite模式:

  • 你想表示对象的部分-整体层次结构。
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

结构

组合模式结构如下
ShowImage

  • Component

    –为组合中的对象声明接口。

    –在适当的情况下,实现所有类共有接口的缺省行为。

    –声明一个接口用于访问和管理Component的子组件。

    –(可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。

  • Leaf

    – 在组合中表示叶节点对象,叶节点没有子节点。

    – 在组合中定义图元对象的行为。

  • Composite

    –定义有子部件的那些部件的行为。

    –存储子部件。

    –在Component接口中实现与子部件有关的操作。

  • Client

    –通过Component接口操纵组合部件的对象。

协作

用户使用Component类接口与组合结构中的对象进行交互。如果接收者是一个叶节点,则直接处理请求。如果接收者是Composite, 它通常将请求发送给它的子部件,在转发请求之前与/或之后可能执行一些辅助操作。

效果

  • 定义了包含基本对象和组合对象的类层次结构

    基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断的递归下去。客户代码中,任何用到基本对象的地方都可以使用组合对象。
  • 简化客户代码

    客户可以一致地使用组合结构和单个对象。通常用户不知道(也不关心)处理的是一个叶节点还是一个组合组件。这就简化了客户代码, 因为在定义组合的那些类中不需要写一些充斥着选择语句的函数。
  • 使得更容易增加新类型的组件

    新定义的Composite或Leaf子类自动地与已有的结构和客户代码一起工作,客户程序不需因新的Component类而改变。
  • 使你的设计变得更加一般化

    容易增加新组件也会产生一些问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用Composite时,你不能依赖类型系统施加这些约束,而必须在运行时刻进行检查。

组合模式实现(Implement)

案例

其实在文件系统中,文件夹也是一种文件,现在我们把文件夹跟文件都抽象成AbstractFile类,文件夹为Directory类,具体的文件为File类,文件夹有增加文件(夹),删除文件(夹),清空文件夹和显示文件夹目录下所有内容,文件可以显示当前文件名。

代码实现

抽象文件类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public abstract class AbstractFile {
private String name;
public AbstractFile(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void listAbstractFiles();
}

文件夹类

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
public class Directory extends AbstractFile {
protected List<AbstractFile> abstractFilesList = new ArrayList<>();
public Directory(String name) {
super(name);
}
public void addAbstractFile(AbstractFile abstractFile) {
abstractFilesList.add(abstractFile);
}
public void removeAbstractFile(AbstractFile abstractFile) {
abstractFilesList.remove(abstractFile);
}
public void removeAllFiles() {
abstractFilesList.clear();
}
@Override
public void listAbstractFiles() {
System.out.print(getName() + "{");
Iterator<AbstractFile> iterator = abstractFilesList.iterator();
while (iterator.hasNext()) {
AbstractFile abstractFile = iterator.next();
abstractFile.listAbstractFiles();
if (iterator.hasNext()) {
System.out.print(", ");
}
}
System.out.print("}");
}
}

文件类

1
2
3
4
5
6
7
8
9
10
public class File extends AbstractFile {
public File(String name) {
super(name);
}
@Override
public void listAbstractFiles() {
System.out.print(getName());
}
}

Client调用

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
public class SafeCompositePatternTest {
@Test
public void safeCompositePatternTest() {
Directory userDirectory = new Directory("Users");
Directory administratorDirectory = new Directory("administrator");
Directory guestDirectory = new Directory("guest");
Directory videoDirectory = new Directory("Video");
Directory musicDirectory = new Directory("Music");
Directory picturesDirectory = new Directory("Picture");
File logFile = new File("logs.txt");
File musicFile1 = new File("Piano.mp3");
File musicFile2 = new File("Violin.mp3");
File videoFile1 = new File("NBA.mp4");
File videoFile2 = new File("WorldCup.mp4");
File pictureFile1 = new File("the Great Wall.jpg");
userDirectory.addAbstractFile(administratorDirectory);
userDirectory.addAbstractFile(guestDirectory);
administratorDirectory.addAbstractFile(videoDirectory);
administratorDirectory.addAbstractFile(musicDirectory);
administratorDirectory.addAbstractFile(picturesDirectory);
administratorDirectory.addAbstractFile(logFile);
videoDirectory.addAbstractFile(videoFile1);
videoDirectory.addAbstractFile(videoFile2);
musicDirectory.addAbstractFile(musicFile1);
musicDirectory.addAbstractFile(musicFile2);
picturesDirectory.addAbstractFile(pictureFile1);
userDirectory.listAbstractFiles();
}
}

父类跟子类之间的层次关系十分清楚,而且公共的接口也是一致的,但是出现了一个新的问题,那就是在创建文件夹和文件对象时必须指定创建类型Directory和File,而不能使用抽象AbstractFile,这样就违背了依赖倒置原则,Client依赖于具体的实现而不是抽象。这种方式叫作安全的组合模式,还有一种组合模式叫作透明的组合模式,将所有方法都在抽象中声明,但是实现类比如File类不需要的方法像addAbstractFile()就抛出不支持操作类型的异常,这样的话依赖问题是解决了,但是程序在运行时将变得不安全,一旦调用到子类不支持的方法程序将抛出异常。透明的组合模式代码如下

抽象文件类

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
public abstract class AbstractFile {
private String name;
protected List<AbstractFile> files = new ArrayList<>();
public AbstractFile(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void addAbstractFile(AbstractFile abstractFile);
public abstract void removeAbstractFile(AbstractFile abstractFile);
public abstract void revoveAllAbstractFiles();
public abstract void listAbstractFiles();
}

文件夹类

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
public class Directory extends AbstractFile {
public Directory(String name) {
super(name);
}
@Override
public void addAbstractFile(AbstractFile abstractFile) {
files.add(abstractFile);
}
@Override
public void removeAbstractFile(AbstractFile abstractFile) {
files.remove(abstractFile);
}
@Override
public void revoveAllAbstractFiles() {
files.clear();
}
@Override
public void listAbstractFiles() {
System.out.print(getName() + "{");
Iterator<AbstractFile> iterator = files.iterator();
while (iterator.hasNext()) {
AbstractFile abstractFile = iterator.next();
abstractFile.listAbstractFiles();
if (iterator.hasNext()) {
System.out.print(",");
}
}
System.out.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
public class File extends AbstractFile {
public File(String name) {
super(name);
}
@Override
public void addAbstractFile(AbstractFile abstractFile) {
throw new UnsupportedOperationException("文件无法进行该操作");
}
@Override
public void removeAbstractFile(AbstractFile abstractFile) {
throw new UnsupportedOperationException("文件无法进行该操作");
}
@Override
public void revoveAllAbstractFiles() {
throw new UnsupportedOperationException("文件无法进行该操作");
}
@Override
public void listAbstractFiles() {
System.out.print(getName());
}
}

Client调用透明的组合模式

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
public class TransparentCompositePatternTest {
@Test
public void transparentCompositePatternTest() {
AbstractFile userDirectory = new Directory("Users");
AbstractFile administratorDirectory = new Directory("administrator");
AbstractFile guestDirectory = new Directory("guest");
AbstractFile videoDirectory = new Directory("Video");
AbstractFile musicDirectory = new Directory("Music");
AbstractFile picturesDirectory = new Directory("Picture");
AbstractFile logFile = new File("logs.txt");
AbstractFile musicFile1 = new File("Piano.mp3");
AbstractFile musicFile2 = new File("Violin.mp3");
AbstractFile videoFile1 = new File("NBA.mp4");
AbstractFile videoFile2 = new File("WorldCup.mp4");
AbstractFile pictureFile1 = new File("the Great Wall.jpg");
userDirectory.addAbstractFile(administratorDirectory);
userDirectory.addAbstractFile(guestDirectory);
administratorDirectory.addAbstractFile(videoDirectory);
administratorDirectory.addAbstractFile(musicDirectory);
administratorDirectory.addAbstractFile(picturesDirectory);
administratorDirectory.addAbstractFile(logFile);
videoDirectory.addAbstractFile(videoFile1);
videoDirectory.addAbstractFile(videoFile2);
musicDirectory.addAbstractFile(musicFile1);
musicDirectory.addAbstractFile(musicFile2);
picturesDirectory.addAbstractFile(pictureFile1);
userDirectory.listAbstractFiles();
}
}

总结

在组合模式中最重要的一点是整体与部分的层次关系,强调的是上级跟下级实现相同的接口,在调用的时候具有一致性。组合模式可以形成树状结构,并且对树状结构的操作非常简单。

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!