组合模式也称为部分整体模式,它比较简单,是将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应对象,借此来忽略对象与对象集合之间的差别。
第十九章 物以类聚——组合模式
1.定义
将对象组合成树形结构以表示“部分—整体”的层次结构,使用户对单个对象和组合对象的使用具有一致性。
2.使用场景
1).表示对象的部分—整体层次结构时。
2).从一个整体中能够独立出部分模块或功能时。
3.简单实现
组合模式主要有三类角色:
Component :抽象节点,为组合中的对象声明接口,适当情况下实现所有类公有接口的缺省(默认)行为。
Composite :树枝节点,实现抽象节点,可以有子节点,一般有存储子节点的方法。
Leaf :树叶节点,不能有子节点。
通过 UML 类图来看看组合模式的结构:
Composite 和 Leaf 都实现了抽象节点,所以在使用的时候 Composite 也可以传递给任意使用抽象节点的方法或者对象,使用起来就跟 Leaf 一样。但是有一个问题就是前面提到 Composite 一般都有存储子节点(可以是 Composite ,也可以是 Leaf )的方法,不然无法完成对子节点的添加、删除等操作,失去了灵活性,那么这个存储方法是应该在抽象节点中声明呢,还是在 Composite 新增呢?两种方式各有利弊。
如果在抽象节点上声明管理子类的方法,可以达到 Component 接口的最大透明化,客户端使用时 Composite 和 Leaf 看起来没有差别,但是这些方法对于 Leaf 是不需要的,甚至会带来一些安全性问题。
那么 UML 类图应该是这样:
如果在 Composite 上新增方法,则又失去了透明性。
一般来说,在组合模式中,相对于安全性,我们更注重透明性,所以一般使用前者,对于子节点不需要的方法,我们可以主动抛出异常或者不做任何处理来解决。
假设这样的场景,电脑硬盘中的某个分区比如 D 盘,在 D 盘新建一个文件夹“程序员”,这个文件夹可以放文件,也可以在这个文件夹中再新建子文件夹,子文件夹中也可以放文件,甚至再新建子文件夹。在处理的时候,我们可以把文件夹包括里面的所有东西看作一个整体,把整个文件夹看作是一个文件,这样就可以统一管理了。
先定义一个抽象文件类,采用透明性高的形式:
public abstract class AbstractFile {
private String name;
public List<AbstractFile> files = new ArrayList<>();
public AbstractFile(String name) {
this.name = name;
}
public String getName() {
return name;
}
public abstract List<AbstractFile> getFiles();
public abstract void addFile(AbstractFile abstractFile);
public abstract void removeFile(AbstractFile abstractFile);
public abstract AbstractFile getFile(int position);
public abstract boolean isDirectory();
}
public class File extends AbstractFile {
public File(String name) {
super(name);
}
@Override
public List<AbstractFile> getFiles() {
throw new UnsupportedOperationException("文件对象不允许查看目录");
}
@Override
public void addFile(AbstractFile abstractFile) {
throw new UnsupportedOperationException("文件对象不允许添加文件");
}
@Override
public void removeFile(AbstractFile abstractFile) {
throw new UnsupportedOperationException("文件对象不允许删除文件");
}
@Override
public AbstractFile getFile(int position) {
throw new UnsupportedOperationException("文件对象不允许查找文件");
}
@Override
public boolean isDirectory() {
return false;
}
}
文件夹对象(树枝节点):
public class Directory extends AbstractFile {
public Directory(String name) {
super(name);
}
@Override
public List<AbstractFile> getFiles() {
return files;
}
@Override
public void addFile(AbstractFile abstractFile) {
files.add(abstractFile);
}
@Override
public void removeFile(AbstractFile abstractFile) {
files.remove(abstractFile);
}
@Override
public AbstractFile getFile(int position) {
return files.get(position);
}
@Override
public boolean isDirectory() {
return true;
}
}
Directory directory1 = new Directory("程序员");
File file1 = new File("Android Studio.exe");
File file2 = new File("Eclipse.exe");
File file3 = new File("Android源码设计模式解析与实战.pdf");
File file4 = new File("Android开发艺术探索.pdf");
Directory directory2 = new Directory("放松");
File file5 = new File("极品飞车.exe");
File file6 = new File("明朝那些事儿.txt");
directory2.addFile(file5);
directory2.addFile(file6);
directory1.addFile(file1);
directory1.addFile(file2);
directory1.addFile(file3);
directory1.addFile(file4);
directory1.addFile(directory2);
if (directory1.isDirectory()) {
for (AbstractFile abstractFile1 : directory1.getFiles()) {
if (abstractFile1.isDirectory()) {
for (AbstractFile abstractFile2 : abstractFile1.getFiles()) {
Log.i(TAG, "abstractFile2:" + abstractFile2.getName());
}
} else {
Log.i(TAG, "abstractFile1:" + abstractFile1.getName());
}
}
}
运行程序,打印如下:
可以看到判断每一个文件都被打印出来了,为什么要判断是否是文件夹之后再遍历其中的文件,是因为我们为了透明性,所以在文件对象中也添加了获取子节点的方法,但是文件对象中是没有子节点的,所以我们主动抛出了一个异常,如果当前对象是一个文件对象还去获取子节点的话,我们增加一行代码:
if (directory1.isDirectory()) {
for (AbstractFile abstractFile1 : directory1.getFiles()) {
if (abstractFile1.isDirectory()) {
for (AbstractFile abstractFile2 : abstractFile1.getFiles()) {
Log.i(TAG, "abstractFile2:" + abstractFile2.getName());
}
} else {
Log.i(TAG, "abstractFile1:" + abstractFile1.getName());
abstractFile1.getFiles();
}
}
}
再次运行,可以看到程序崩溃了:
程序报了我们自己添加的“文件对象不允许查看目录”的异常。
这个例子的主要思想就是把文件和文件夹都统一处理,组合模式在 Android 源码中体现得非常明显, View 和 ViewGroup 就是最典型的例子,具体源码等读第二遍的时候再详细琢磨。
4.总结
组合模式用得倒不算多,多适用于界面 UI 的架构设计上,还有像文件目录选择器这样是最适合组合模式的了,组合模式锁提供的属性层次结构使我们能一视同仁的对待单个对象和对象集合,但是又违背了的单一原则,而且组合模式是通过继承实现的,缺少弹性,所以使用时也要权衡利弊。
优点
1.)可以清楚地定义分层次的复杂对象,表示对象的全部或者部分层次,让高层模块忽略层次之间的差异,方便控制整个层次结构。
2.)可以一致地使用其中单个对象或一个组合结构,而不必关心操作的是单个对象还是组合,简化高层模块的代码。
3.)增加新的树叶节点和树枝节点都很方便,符合开闭原则。
缺点:
1).新增节点时不好对节点类型进行限制,因为都来自同一个抽象层。