结构型模式三:《组合模式》
什么是组合模式
组合模式是一种设计模式,它允许将对象组合成树形结构,以表示“部分-整体”
的层次结构。组合模式使得客户端对单个对象
和组合对象
的使用具有一致性
,因为它们都具有相同的接口
。
分析:
在树形目录结构中,包含文件和文件夹两类不同的元素
在文件夹中可以包含文件,还可以继续包含子文件夹
在文件中不能再包含子文件或者子文件夹
文件夹: 容器(Container)
文件: 叶子(Leaf)
当容器对象的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象并调用执行,牵一而动百,其中使用了递归调用的机制来对整个结构进行处理
由于容器对象和叶子对象在功能上的区别,在使用这些对象的代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们,因为对于这些对象的区别对待将会使程序非常复杂.
组合模式的结构
组合模式包含以下3个角色:
Component(抽象构件)
Leaf(叶子构件)
Composite(容器构件)
具体实例
需求: 某软件公司欲开发一个杀毒(Antivirus)软件,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。 现使用组合模式来设计该杀毒软件的整体框架。
抽象文件类(抽象构建类)
该抽象类为定义文件的基本操作方法
public 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();
}
具体文件类 (叶子构建类)
继承抽象类,实现杀毒方法,而添加删除属于组合对象的特有操作,因此实现为不支持该方法。
// 图像文件类
public 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 + "'进行杀毒");
}
}
// 文本文件类
public 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 + "'进行杀毒");
}
}
// 视频文件类
public 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 + "'进行杀毒");
}
}
文件夹类(容器构建类)
public class Folder extends AbstractFile {
//定义集合fileList,用于存储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 + "'进行杀毒"); //模拟杀毒
//递归调用成员构件的killVirus()方法
for(Object obj : fileList) {
((AbstractFile)obj).killVirus();
}
}
}
客户端调用类
public class Client {
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);
//从“Sunny的资料”结点开始进行杀毒操作
folder1.killVirus();
}
}
输出:
****对文件夹'Sunny的资料'进行杀毒
****对文件夹'图像文件'进行杀毒
----对图像文件'小龙女.jpg'进行杀毒
----对图像文件'张无忌.gif'进行杀毒
****对文件夹'文本文件'进行杀毒
----对文本文件'九阴真经.txt'进行杀毒
----对文本文件'葵花宝典.doc'进行杀毒
****对文件夹'视频文件'进行杀毒
----对视频文件'笑傲江湖.rmvb'进行杀毒
可以发现,功能正常完成。
结果分析
如果需要更换操作节点,例如只对文件夹“文本文件”进行杀毒,客户端代码只需修改一行即可
在具体实现时,可以创建图形化界面让用户来选择所需操作的根节点,无须修改源代码,符合开闭原则
上面的属于透明组合模式:
抽象构件Component中声明了所有用于管理成员对象的方法,包括add()
、remove()
,以及getChild()
等方法
在客户端看来,叶子对象与容器对象所提供的方法是一致的,客户端可以一致地对待所有的对象
缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的
还有一种是安全组合模式:
抽象构件Component中没有声明任何用于管理成员对象的方法,而是在Composite类中声明并实现这些方法
对于叶子对象,客户端不可能调用到这些方法
缺点是不够透明,客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件
模式优缺点
模式优点:
可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制
客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
增加新的容器构件和叶子构件都很方便,符合开闭原则
为树形结构的面向对象实现提供了一种灵活的解决方案
模式缺点:
在增加新构件时很难对容器中的构件类型进行限制, 下面是解释。
组合模式在增加新构件时很难对容器中的构件类型进行限制,是因为组合模式强调了构件对象的统一接口,这使得容器对象无法区分不同类型的构件对象。