组合模式一般用于树形结构的处理,比如文件系统中对文件的处理
1. 引出问题
需求:设计一个杀毒软件,该软件既可以对文件夹杀毒,也可以对单独的文件进行杀毒,同时可以为不同类型的文件(如图片或文档)提供不同的杀毒方式
在图中的树形目录结构中,可以分为文件夹和具体文件,文件夹内可以包含文件夹或文件,又称容器,而具体文件不会再包含任何其他文件,故又称为叶子
现在编写文件夹与具体文件的代码
图像文件类:
public class ImageFile {
private String name;
public ImageFile(String name) {
this.name = name;
}
public void killVirus(){
//模拟杀毒
System.out.println("----对图像文件: " + name + ",进行杀毒");
}
}
文本文件类:
public class TextFile {
private String name;
public TextFile(String name) {
this.name = name;
}
public void killVirus(){
//模拟杀毒
System.out.println("----对文本文件: " + name + ",进行杀毒");
}
}
文件夹类:
public class Folder {
private String name;
private List<Folder> folderList = new ArrayList<>();
private List<ImageFile> imageList = new ArrayList<>();
private List<TextFile> textList = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void addFolder(Folder folder){
folderList.add(folder);
}
public void addImage(ImageFile image){
imageList.add(image);
}
public void addText(TextFile text){
textList.add(text);
}
//此处省略三个用于删除成员的remove方法
//此处省略三个用于获取成员的get方法
public void killVirus(){
System.out.println("****对文件夹: " + name + ",进行杀毒");
//对Folder类型的成员,调用Folder类的杀毒方法
for (Folder folder : folderList) {
folder.killVirus();
}
//对Image类型的成员,调用ImageFile类的杀毒方法
for (ImageFile imageFile : imageList) {
imageFile.killVirus();
}
//对Text类型的成员,调用TextFile类的杀毒方法
for (TextFile textFile : textList) {
textFile.killVirus();
}
}
}
编写客户端进行测试:
public class Client {
public static void main(String[] args) {
Folder folder1 = new Folder("Sunny的资料");
Folder folder2 = new Folder("图像文件");
Folder folder3 = new Folder("文本文件");
ImageFile imageFile1 = new ImageFile("小龙女.jpg");
ImageFile imageFile2 = new ImageFile("张无忌.gif");
TextFile textFile1 = new TextFile("九阴真经.txt");
TextFile textFile2 = new TextFile("葵花宝典.doc");
folder2.addImage(imageFile1);
folder2.addImage(imageFile2);
folder3.addText(textFile1);
folder3.addText(textFile2);
folder1.addFolder(folder2);
folder1.addFolder(folder3);
folder1.killVirus();
}
}
测试结果如下:
存在的问题:
- 文件夹类Folder内部较为复杂,既需要运用集合类存储各类的文件夹和文件,而且需要分别实现添加、删除、获取的方法,内部代码冗余情况较为严重
- 系统中没有提供抽象层,在客户端中需要具体的指定Folder、ImageFile、TextFile的类型,不具备通用性,无法进行统一处理
- 系统的可扩展性较差,如果新增一种文件格式如视频文件VideoFile,则需要对Folder类进行大量改动
2. 组合模式介绍
定义:组合多个对象形成树形结构以表示具有“部分—整体”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,又可以称为“部分—整体”(Part-Whole)模式
引入抽象构件类Component,所有容器和叶子类都是它的子类,客户端也针对它进行编程
组合模式中包含以下三种角色:
- Component(抽象构件)
- Leaf(叶子构件)
- Composite(容器构件)
3. 以组合模式重构案例
抽象构件:AbstractFile
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();
}
叶子构件1:ImageFile
public class ImageFile extends AbstractFile {
private String name;
public ImageFile(String name) {
this.name = name;
}
@Override
public void add(AbstractFile file) {
System.out.println("对不起,不支持此方法!");
}
@Override
public void remove(AbstractFile file) {
System.out.println("对不起,不支持此方法!");
}
@Override
public AbstractFile getChild(int i) {
System.out.println("对不起,不支持此方法!");
return null;
}
@Override
public void killVirus() {
//模拟杀毒
System.out.println("----对图像文件: " + name + ",进行杀毒");
}
}
叶子构件2:TextFile
public class TextFile extends AbstractFile {
private String name;
public TextFile(String name) {
this.name = name;
}
@Override
public void add(AbstractFile file) {
System.out.println("对不起,不支持此方法!");
}
@Override
public void remove(AbstractFile file) {
System.out.println("对不起,不支持此方法!");
}
@Override
public AbstractFile getChild(int i) {
System.out.println("对不起,不支持此方法!");
return null;
}
@Override
public void killVirus() {
//模拟杀毒
System.out.println("----对文本文件: " + name + ",进行杀毒");
}
}
叶子构件3:VideoFile
public class VideoFile extends AbstractFile {
private String name;
public VideoFile(String name) {
this.name = name;
}
@Override
public void add(AbstractFile file) {
System.out.println("对不起,不支持此方法!");
}
@Override
public void remove(AbstractFile file) {
System.out.println("对不起,不支持此方法!");
}
@Override
public AbstractFile getChild(int i) {
System.out.println("对不起,不支持此方法!");
return null;
}
@Override
public void killVirus() {
//模拟杀毒
System.out.println("----对视频文件: " + name + ",进行杀毒");
}
}
容器构件:Folder
public class Folder extends AbstractFile {
private List<AbstractFile> fileList = new ArrayList<>();
private String name;
public Folder(String name) {
this.name = name;
}
@Override
public void add(AbstractFile file) {
fileList.add(file);
}
@Override
public void remove(AbstractFile file) {
fileList.remove(file);
}
@Override
public AbstractFile getChild(int i) {
return fileList.get(i);
}
@Override
public void killVirus() {
//模拟杀毒
System.out.println("****对文件夹: " + name + ", 进行杀毒");
//递归对子文件进行处理
for (AbstractFile file : fileList) {
file.killVirus();
}
}
}
编写客户端测试类:Client
public class Client {
public static void main(String[] args) {
Folder folder1 = new Folder("Sunny的资料");
Folder folder2 = new Folder("图像文件");
Folder folder3 = new Folder("文本文件");
Folder folder4 = new Folder("视频文件");
ImageFile imageFile1 = new ImageFile("小龙女.jpg");
ImageFile imageFile2 = new ImageFile("张无忌.gif");
TextFile textFile1 = new TextFile("九阴真经.txt");
TextFile textFile2 = new TextFile("葵花宝典.doc");
VideoFile videoFile = new VideoFile("倚天屠龙记.avi");
folder2.add(imageFile1);
folder2.add(imageFile2);
folder3.add(textFile1);
folder3.add(textFile2);
folder4.add(videoFile);
folder1.add(folder2);
folder1.add(folder3);
folder1.add(folder4);
//以folder1为入口,开始杀毒
folder1.killVirus();
}
}
测试结果如下:
4. 透明组合模式与安全组合模式
上一节使用组合模式对案例进行了重构,重构后的系统增强了程序的可扩展性,当需要新增文件类型时,只需要让新的文件类型类作为叶子构件继承抽象构件并实现方法即可
然而,现有的系统仍然存在一个问题:每次创建新的构件的时候,都要重复实现add、remove和get方法
4.1. 透明组合模式
将add等方法直接在AbstractFile中做默认实现
修改代码:
public abstract class AbstractFile {
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 abstract void killVirus();
}
修改之后,虽然叶子构件无需重复实现这几个方法,但是它们实际上仍然继承到了这几个它们并不需要用到的方法,客户端仍然可以调用抽象构件中的add等方法
4.2. 安全组合模式
在抽象构件中不声明add等方法,到容器构件中再创建方法
修改代码:
public abstract class AbstractFile {
public abstract void killVirus();
}
修改之后,客户端如果通过抽象构件创建容器构件的话,无法调用在容器构件中实现的add、remove和get方法,即导致容器构件中新增的方法对客户端不可见,若要完整实现预期的功能,就不能够再通过抽象构件统一定义对象