1 基本介绍
组合模式为:组合多个对象形成树形结构以表示具有”整体——部分“关系的层次结构。
组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。
在组合模式中引入了抽象构件类Component
,它是所有容器类和叶子类的公共父类,客户端针对Component
进行编程。
该模式的关键在于定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。
同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构
2 设计分析
2.1 栗子
杀毒软件框架设计,客户可以一致地对待文件和文件夹
代码大概如下
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| package DesignPatterns.JavaDesign.Composite;
import java.util.ArrayList;
public class Test { public static void main(String[] args) { AbstractFile file1, file2, file3, file4, file5, folder1, folder2, folder3, folder4; folder1 = new Folder("Sunny的资料"); folder2 = new Folder("图像文件"); folder3 = new Folder("文本文件"); folder4 = new Folder("视频文件"); file1 = new ImageFile("小龙女.jpg"); file2 = new ImageFile("张无忌.gif"); file3 = new TextFile("九阴真经.txt"); file4 = new TextFile("葵花宝典.doc"); file5 = new VideoFile("笑傲江湖.rmvb"); folder2.add(file1); folder2.add(file2); folder3.add(file3); folder3.add(file4); folder4.add(file5); folder1.add(folder2); folder1.add(folder3); folder1.add(folder4); folder1.killVirus(); } }
abstract class AbstractFile { public abstract void add(AbstractFile file);
public abstract void remove(AbstractFile file);
public abstract AbstractFile getChild(int i);
public abstract void killVirus(); }
class ImageFile extends AbstractFile { private String name;
public ImageFile(String name) { this.name = name; }
public void add(AbstractFile file) { System.out.println("对不起,不支持该方法!"); }
public void remove(AbstractFile file) { System.out.println("对不起,不支持该方法!"); }
public AbstractFile getChild(int i) { System.out.println("对不起,不支持该方法!"); return null; }
public void killVirus() { System.out.println("----对图像文件'" + name + "'进行杀毒"); } }
class TextFile extends AbstractFile { private String name;
public TextFile(String name) { this.name = name; }
public void add(AbstractFile file) { System.out.println("对不起,不支持该方法!"); }
public void remove(AbstractFile file) { System.out.println("对不起,不支持该方法!"); }
public AbstractFile getChild(int i) { System.out.println("对不起,不支持该方法!"); return null; }
public void killVirus() { System.out.println("----对文本文件'" + name + "'进行杀毒"); } }
class VideoFile extends AbstractFile { private String name;
public VideoFile(String name) { this.name = name; }
public void add(AbstractFile file) { System.out.println("对不起,不支持该方法!"); }
public void remove(AbstractFile file) { System.out.println("对不起,不支持该方法!"); }
public AbstractFile getChild(int i) { System.out.println("对不起,不支持该方法!"); return null; }
public void killVirus() { System.out.println("----对视频文件'" + name + "'进行杀毒"); } }
class Folder extends AbstractFile { private ArrayList<AbstractFile> fileList = new ArrayList<AbstractFile>(); private String name;
public Folder(String name) { this.name = name; }
public void add(AbstractFile file) { fileList.add(file); }
public void remove(AbstractFile file) { fileList.remove(file); }
public AbstractFile getChild(int i) { return (AbstractFile) fileList.get(i); }
public void killVirus() { System.out.println("****对文件夹'" + name + "'进行杀毒"); for (Object obj : fileList) { ((AbstractFile) obj).killVirus(); } } }
|
2.2 原来方案的问题
由于在AbstractFile中声明了大量用于管理和访问成员构件的方法,例如add()、remove()等方法,我们不得不在新增的文件类中实现这些方法,提供对应的错误提示和异常处理
解决方案1:将叶子构件的add()、remove()等方法的实现代码移至AbstractFile类中,由AbstractFile提供统一的默认实现
解决方案2:除此之外,还有一种解决方法是在抽象构件AbstractFile中不声明任何用于访问和管理成员构件的方法
根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式
2.3 透明组合模式
透明组合模式中,抽象构件Component中声明了所有用于管理成员对象的方法,包括add()、remove()以及getChild()等方法,这样做的好处是确保所有的构件类都有相同的接口。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的。叶子对不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供add()、remove()以及getChild()等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)。
2.4 安全组合模式
安全组合模式中,在抽象构件Component中没有声明任何用于管理成员对象的方法,而是在Composite类中声明并实现这些方法
安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。在实际应用中,安全组合模式的使用频率也非常高,在Java AWT中使用的组合模式就是安全组合模式。
3 总结
优缺点分析
优点
- 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
- 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”
- 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
缺点
在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂
适用场景
- 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们
- 在一个使用面向对象语言开发的系统中需要处理一个树形结构
- 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。