设计模式——组合模式(文件夹与文件)

本文简单介绍组合模式,以系统文件和文件夹为例。

image-20181223141136417

一、概述

定义:组合模式(Composite pattern)将对象整合到树状结构中来表示整体/部分的层次关系,在树状结构中包括对象和对象组合。组合模式能让客户用一致的方式处理个别对象的对象组合。

组合模式主要解决两个问题
1.部分-整体的层级结构以树状结构来表现
2.部分整体的层级结构中,对象和对象组合要以一致方式处理

二、结构

UML:

image-20181221071228741

主要角色:

抽象构件类Compnent):为所有对象定义了一个接口,无论是叶节点还是组合。

叶构件类(Leaf):继承了抽象构件类,但是没有子构件了,虽然继承了add,remove,getChildren方法,但是对于叶节点来说没什么意义,不过为了保持透明性,我们坚持这么做。

树枝构件类(Component):继承了抽象构件类,持有一个子构件类容器口。

三、系统文件目录

系统文件目录是一个典型的包括叶构件(文件)和树枝构件(文件夹)的组合模式。

image-20181221201327097

当然文件夹和文件操作上还是有区别的,不过,我在这里就让文件夹和文件都实现相同的接口,完成最理想的组合的组合模式——放弃安全性,追求透明性。

定义一个抽象接口,为了保证可以使用户不关心究竟是什么,所有文件/文件夹都要实现这个接口。

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
public abstract class FileInterface {
// 添加文件
void add(FileInterface file) {
throw new UnsupportedOperationException();
}

// 删除文件
void remove(String fileName) {
throw new UnsupportedOperationException();
}

// 获取文件
Set<FileInterface> getChildren() {
throw new UnsupportedOperationException();
}

// 获取文件名字
String getName(){
throw new UnsupportedOperationException();
}

// 获取文件描述
String getDescription(){
throw new UnsupportedOperationException();
};

// 运行程序
void run(){
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
public class Directory implements FileInterface {
private String name;
private String desc;
// 文件夹要持有文件列表
private Set<FileInterface> set = new HashSet<>();

public Directory(String name, String desc) {
this.name = name;
this.desc = desc;
}

@Override
public void add(FileInterface file) {
set.add(file);
}

@Override
public void remove(String fileName) {
if (fileName==null)
throw new UnsupportedOperationException("请输入文件名");
Iterator<FileInterface> iterator = this.set.iterator();
while (iterator.hasNext()){
FileInterface next = iterator.next();
if (fileName.equals(next.getName())){
iterator.remove();
}
}
}

@Override
public Set<FileInterface> getChildren() {
return this.set;
}

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

@Override
public String getDescription() {
return this.desc;
}
// 重写了equals和hashcode方法,不允许出现重名文件
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FileInterface)) return false;

FileInterface directory = (FileInterface) o;

return name.equals(directory.getName());
}

@Override
public int hashCode() {
return name.hashCode();
}

@Override
public String toString() {
return "Directory{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
", set=" + set +
'}';
}
}

写一个exe文件类

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
public class ExeFile implements FileInterface {
private String name;
private String desc;

public ExeFile(String name, String desc) {
this.name = name;
this.desc = desc;
}

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

@Override
public String getDescription() {
return this.desc;
}

@Override
public void run() {
System.out.println("运行程序");
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FileInterface)) return false;

FileInterface directory = (FileInterface) o;

return name.equals(directory.getName());
}

@Override
public int hashCode() {
return name.hashCode();
}

@Override
public String toString() {
return "ExeFile{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}

这两个类都继承了FileIntercepter抽象类并实现了所有方法,可以等同看待,虽然有些方法并不是两个类都能用的,一定程度上丧失了安全性(程序员调用时需要指到方法的具体实现),不过为了保持透明性(方法同等看待),我们仍选择了全部实现,对于个别方法使用抛出不支持操作异常处理!

下面看测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args) {
Directory c = new Directory("C:", "C盘啊");
ExeFile system = new ExeFile("system.exe", "系统程序");
c.add(system);

Directory animals = new Directory("animals", "动物程序文件夹");
ExeFile dog = new ExeFile("dog.exe", "小狗程序");
ExeFile pig = new ExeFile("pig.exe","小猪程序");

animals.add(dog);
animals.add(pig);

c.add(animals);

animals.remove("dog.exe");
System.out.println(c);
}
}
---->结果
Directory{name='C:', desc='C盘啊', set=[ExeFile{name='system.exe', desc='系统程序'}, Directory{name='animals', desc='动物程序文件夹', set=[ExeFile{name='pig.exe', desc='小猪程序'}]}]}

四、优缺点

优点

  1. 组合模式可以很容易的增加新的构件。
  2. 使用组合模式可以使客户端变的很容易设计,因为客户端可以对组合和叶节点一视同仁。

缺点

  1. 使用组合模式后,控制树枝构件的类型不太容易。
  2. 用继承的方法来增加新的行为很困难。

组合的适用场合:

  1. 你想表示对象的部分-整体层次结构。

  2. 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

五、总结

组合模式是对象的结构模式,重点掌握树状结构和将叶节点和组合节点同等看待。

当组合模式与迭代器模式结合时,调用者甚至可以忽略掉组合模式的结构,这里我必须推荐Head First 设计模式中迭代器模式与组合模式这一章,你会发现组合模式与迭代器结合的巨大威力!

此外也有人会为了安全性,将非共性方法不在抽象类中声明,这样虽然安全了,但是调用者就不能将组合模式的叶节点与组合节点同等看待,这并不符合我们的目的,所以我们还是选择本文这种实现方式。

本文首发于cdream个人博客

欢迎转载,转载请注明出处!


  1. Head First 设计模式,Eric Freeman &Elisabeth Freeman with Kathy Sierra & Bert Bates
  2. Composite pattern,wiki
  3. 《JAVA与模式》之合成模式
0%