组合模式

组合模式

  关于组合模式的理解,结合计算机的文件管理系统是一个很好的理解方式。
我们都知道在计算机的一个文件夹中可能包含文件以及文件夹,我们可以将一个文件管理系统想象成一棵树,其中一个文件就相当于一个叶子(leaf)节点,一个文件夹就相当于一个分支(Branch)节点,而一个分支节点又可能包含其他的叶子节点或分支节点,我们将应用程序中类似这种包含叶子节点以及分支节点的结构统一称为树形结构。
  对于树形结构,当我们需要对一个文件夹的所有文件进行操作时,需要遍历该文件夹下的所有文件及文件夹中的所有文件,由于文件与文件夹的区别以及包含关系,我们需要区别对待文件及文件夹,这将使得问题的复杂性大大提升。
  而组合模式就是为了解决这类问题而产生的,它提供一种的结构可以让叶子节点与分支节点有具有一致性,从而方便程序进行相关操作。

定义及UML类图

组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即分支对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。

组合模式的UML类图如下:

Composite

根据定义及UML图我们定义了一个组合抽象对象Component,该对象包含对单个对象的操作方法operation()以及对组合对象的集合操作add(Component c)remove(Component c)getChild(int i),而叶子对象与组合对象均继承自这个组合抽象对象,当我们在程序中使用时这个抽象对象既可以当叶子对象又可以当组合对象,而客户端针对该抽象对象编程时可以不用考虑它是叶子对象还是组合对象。

代码实现

还是拿计算机文件管理系统举例,假如我们现在需要对该文件系统中的所有文件进行杀毒操作。

首先定义一个抽象文件类,包含文件及文件夹的相关操作

1
2
3
4
5
6
7
8
9
10
11
public abstract class AbstractFile {

public abstract void add(AbstractFile abstractFile);

public abstract void remove(AbstractFile abstractFile);

public abstract AbstractFile getFile(int i);

public abstract void killVirus();

}

再分别定义一个文件类以及文件夹类

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
public class File extends AbstractFile {
private String name;

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

@Override
public void add(AbstractFile abstractFile) {
System.out.println("不支持该操作");
}

@Override
public void remove(AbstractFile abstractFile) {
System.out.println("不支持该操作");
}

@Override
public AbstractFile getFile(int i) {
System.out.println("不支持该操作");
return null;
}

@Override
public void killVirus() {
System.out.println("======文件 《"+name+ "》 执行杀毒操作======");
}
}



public class Folder extends AbstractFile{
private List<AbstractFile> abstractFileList;
private String name;

public Folder(String name) {
this.name = name;
abstractFileList = new ArrayList<>();
}

@Override
public void add(AbstractFile abstractFile) {
abstractFileList.add(abstractFile);
}

@Override
public void remove(AbstractFile abstractFile) {
abstractFileList.remove(abstractFile);
}

@Override
public AbstractFile getFile(int i) {
return abstractFileList.get(i);
}

@Override
public void killVirus() {
System.out.println("++++++对文件夹 "+name+ " 进行杀毒++++++");

for (AbstractFile abstractFile : abstractFileList) {
abstractFile.killVirus();
}
}
}
```

可以看到在文件类中,我们过滤了文件夹所有的相关操作方法`add()`、`remove()`、`getFile()`,而在文件夹类中,我们定义了一个抽象文件类的集合,同时在杀毒方法`killVirus()`中遍历集合,并调用集合元素的`killVirus()`方法。

客户端代码

public class Client {

public static void main(String[] args){
    AbstractFile file1 = new File("文件1.txt");
    AbstractFile file2 = new File("文件2.jpg");
    AbstractFile file3 = new File("文件3.rmvb");
    AbstractFile file4 = new File("文件4.png");
    AbstractFile file5 = new File("文件5.md");
    AbstractFile file6 = new File("文件6.txt");
    AbstractFile file7 = new File("文件7.html");

    AbstractFile folderA = new Folder("文件夹A");
    AbstractFile folderB = new Folder("文件夹B");
    AbstractFile folderC = new Folder("文件夹C");
    AbstractFile folderRoot = new Folder("根文件夹");

    folderA.add(file1);
    folderA.add(file2);

    folderC.add(file3);
    folderC.add(file4);

    folderB.add(file5);
    folderB.add(file6);
    folderB.add(folderC);

    folderRoot.add(file7);
    folderRoot.add(folderA);
    folderRoot.add(folderB);

    folderRoot.killVirus();
}

}

1
2

运行结果

++++++对文件夹 根文件夹 进行杀毒++++++
======文件 《文件7.html》 执行杀毒操作======
++++++对文件夹 文件夹A 进行杀毒++++++
======文件 《文件1.txt》 执行杀毒操作======
======文件 《文件2.jpg》 执行杀毒操作======
++++++对文件夹 文件夹B 进行杀毒++++++
======文件 《文件5.md》 执行杀毒操作======
======文件 《文件6.txt》 执行杀毒操作======
++++++对文件夹 文件夹C 进行杀毒++++++
======文件 《文件3.rmvb》 执行杀毒操作======
======文件 《文件4.png》 执行杀毒操作======
`
从结果我们可以看见,客户端仅调用了根目录的killVirus()方法,便完美且优雅的实现了所有文件的递归杀毒。

透明组合模式与安全组合模式

  如果稍微仔细一点的朋友就会发现上面代码实现的组合模式并不是完全符合上面的UML类图,在上面UML类图中叶子对象仅仅只有操作方法,而不包含组合对象的关于集合的操作方法,但是在上面的代码中,我们的叶子节点依然是有add()remove()getFile()这些方法的,只是我们在方法的实现中人为的抛出异常,或其他处理。
  其实上面代码实现的是组合模式中的一种 透明组合模式

透明组合模式

透明组合模式中,抽象组合对象中声明了所有子类成员对象的方法,子类叶子对象和组合对象都实现它的所有方法

composite_trans

这样的好处是,对于客户端而言,叶子对象与组合对象所提供的方法是一样的,客户端可以相同的对待所有对象。但由于叶子对象在实际中是不包含下一层成员的,所以我们在编码时需要时刻记得处理叶子对象中无用的组合操作方法,否则在运行中可能出错。为了避免这种情况,还有另一种组合模式-安全组合模式

安全组合模式

安全组合模式中,抽象组合对象中不包含组合对象中管理成员的方法,管理成员的方法只在组合对象中声明

composite_trans

安全组合模式避免了透明组合模式中运行错误的情况,但客户端对于安全组合模式中的叶子对象与组合对象需要区别处理,就违背了面向抽象编程的思想。
实际运用的中安全组合模式的使用频率还是比较高一点,毕竟相比较程序运行错误,大家还是宁愿违背一点设计模式思想。

小结

  组合模式使用面向对象的思想为树形结构提供了一种优雅的处理,使得树形结构在代码中的层次分明,而对于客户端来说,所需处理的只有一种对象,而不用去管理内部复杂的嵌套。在组合模式中,添加新的叶子对象和组合对象都很方便,符合“开闭原则”。

源码:https://github.com/lichenming0516/DesignPattern

坚持原创技术分享,您的支持将鼓励我继续创作!