组合模式

定义

将对象组合成树形结构以表示“整体-部分”的层次结构
组合模式使客户端对单个对象和组合对象保持一致的方式处理

适用场景

  • 希望客户端可以忽略组合对象与单个对象的差异时
  • 处理一个树形结构时

优点和缺点

优点

  • 清楚地定义分层次的复杂对象,表示对象的全部或部分层次
  • 让客户端忽略了层次的差异,方便对整个层次结构进行控制
  • 简化客户端代码
  • 符合开闭原则

缺点

  • 限制类型时会较为复杂
  • 使设计变得更加抽象

结构

主要角色:
抽象构件角色: 它的主要作用是为树叶构件和树枝构件声明公共接口,并实现他们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理之类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
树叶构件角色: 是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
树枝构件角色: 是组合中的分支节点对象,它有子节点,用户继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含add()remove()getChild()等方法。

代码实现

假设我们现在有一个文件夹,里面有若干个文件,我们现在需要用对象来存储,需要表示清楚其中的层级关系
首先创建抽象构件角色,根据业务情况,来控制方法是否是必须需要重写的,也可以使用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class FileComponent {

protected void add(FileComponent fileComponent) {
throw new UnsupportedOperationException("无权操作添加方法");
}

protected void remove(FileComponent fileComponent) {
throw new UnsupportedOperationException("无权操作删除方法");
}

protected String getName() {
throw new UnsupportedOperationException("无权操作获取名称方法");
}

protected Integer getSize() {
throw new UnsupportedOperationException("无权操获取大小方法除方法");
}

protected abstract void 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 FileDoc extends FileComponent {

private String name;
private Integer size;

public FileDoc(String name, Integer size) {
this.name = name;
this.size = size;
}

@Override
public String getName() {
return this.name;
}

@Override
public Integer getSize() {
return this.size;
}

@Override
protected void print() {
System.out.println("文件的名称是:" + this.name + " 文件的大小是:" + this.size);
}
}

树枝构件角色,文件夹类,可以存放或者删除文件,拥有名称和层级属性,文件夹本身是没有大小的,但是可以依托下面文件的大小来计算大小,所以文件夹类可以实现所有的方法

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
public class FolderDoc extends FileComponent {

private final List<FileComponent> components = new ArrayList<>();

private String name;

private Integer level;

public FolderDoc(String name, Integer level) {
this.name = name;
this.level = level;
}

@Override
protected String getName() {
return this.name;
}

@Override
protected Integer getSize() {
return components.stream().mapToInt(FileComponent::getSize).sum();
}

@Override
protected void add(FileComponent fileComponent) {
components.add(fileComponent);
}

@Override
protected void remove(FileComponent fileComponent) {
components.remove(fileComponent);
}

@Override
protected void print() {
System.out.println(this.name + " size=" + getSize());
for (FileComponent component : components) {
if (this.level != null){
for (int i = 0; i < this.level; i++) {
System.out.print(" ");
}
}
component.print();
}
}
}

具体调用,我们在视频文件夹下存放了两部电影,以及一个火影忍者文件夹,文件夹下又有三集电视剧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) {
FileComponent fileComponent = new FileDoc("倩女幽魂",400);
FileComponent fileComponent1 = new FileDoc("僵尸先生",300);

FileComponent fileComponent2 = new FolderDoc("火影忍者",2);
FileComponent fileComponent3 = new FileDoc("火影忍者第一集",45);
FileComponent fileComponent4 = new FileDoc("火影忍者第二集",56);
FileComponent fileComponent5 = new FileDoc("火影忍者第三集",47);

fileComponent2.add(fileComponent3);
fileComponent2.add(fileComponent4);
fileComponent2.add(fileComponent5);

FileComponent folder = new FolderDoc("视频",1);
folder.add(fileComponent);
folder.add(fileComponent1);
folder.add(fileComponent2);

folder.print();
}
}

输出结果

1
2
3
4
5
6
7
视频  size=848
文件的名称是: 倩女幽魂 文件的大小是: 400
文件的名称是: 僵尸先生 文件的大小是: 300
火影忍者 size=148
文件的名称是: 火影忍者第一集 文件的大小是: 45
文件的名称是: 火影忍者第二集 文件的大小是: 56
文件的名称是: 火影忍者第三集 文件的大小是: 47

UML类图