组合模式(Composite Pattern):
组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。
组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,
组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。
在组合模式中引入了抽象构件类Component,它是所有容器类和叶子类的公共父类,客户端针对
Component进行编程。组合模式结构如图
包含角色:
Component(抽象构件):
它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。
在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
Leaf(叶子构件):
它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。
对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
Composite(容器构件):
它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,
它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,
在其业务方法中可以递归调用其子节点的业务方法。
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,
无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,
在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。
如果不使用组合模式,客户端代码将过多地依赖于容器对象复杂的内部实现结构,容器对象内部实现结构的变化将引起客户
代码的频繁变化,带来了代码维护复杂、可扩展性差等弊端。组合模式的引入将在一定程度上解决这些问题。
组合模式-树形结构的处理
树形结构在软件中随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组
织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的问题,组合模式通
过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致性地
处理树形结构中的叶子节点(不包含子节点的节点)和容器节点(包含子节点的节点)。
欲开发一个杀毒(AntiVirus)软件,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。
该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,
例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。
注:
博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
如果不使用组合模式,可能会这样实现
1、新建图像文件类
//图像文件类
public class ImageFile {
private String name;
public ImageFile(String name) {
this.name = name;
}
public void killVirus(){
System.out.println("对图像文件"+name+"进行杀毒");
}
}
2、新建文本文件类
//文本文件类
public class TextFile {
private String name;
public TextFile(String name) {
this.name = name;
}
public void killVirus(){
System.out.println("对文本文件"+name+"进行杀毒");
}
}
3、新建文件夹类
//文件夹类
public class Folder {
private String name;
//用于存储Folder类型的成员
private ArrayList folderList = new ArrayList();
//用于存储ImageFile类型的成员
private ArrayList imageList = new ArrayList();
//用于存储TextFile类型的成员
private ArrayList textList = new ArrayList();
public Folder(String name) {
this.name = name;
}
//增加新的Folder类型的成员
public void addFolder(Folder folder){
folderList.add(folder);
}
//增加新的ImageFile类型的成员
public void addImageFile(ImageFile imageFile){
imageList.add(imageFile);
}
//增加新的TextFile类型的成员
public void addTextFile(TextFile textFile){
textList.add(textFile);
}
//此处省略三种类型的删除成员的方法、获取成员的方法
public void killVirus(){
System.out.println("对文件夹"+name+"进行杀毒");
//如果是Folder类型的成员,递归调用Folder的killVirus方法
for (Object obj : folderList) {
((Folder)obj).killVirus();
}
//如果是ImageFile类型
for (Object obj : imageList) {
((ImageFile)obj).killVirus();
}
//如果是TextFile类型
for (Object obj : textList) {
((TextFile)obj).killVirus();
}
}
}
4、客户端调用示例如下
public static void main(String[] args) {
Folder folder1,folder2,folder3;
folder1 = new Folder("工作资料");
folder2 = new Folder("图片文件");
folder3 = new Folder("文本文件");
ImageFile imageFile1,imageFile2;
imageFile1 = new ImageFile("背景图.jpg");
imageFile2 = new ImageFile("示例图.gif");
TextFile textFile1,textFile2;
textFile1 = new TextFile("接口.doc");
textFile2 = new TextFile("示例.txt");
folder2.addImageFile(imageFile1);
folder2.addImageFile(imageFile2);
folder3.addTextFile(textFile1);
folder3.addTextFile(textFile2);
folder1.addFolder(folder2);
folder1.addFolder(folder3);
folder1.killVirus();
}
}
分析存在的问题
(1) 文件夹类Folder的设计和实现都非常复杂,需要定义多个集合存储不同类型的成员,而且需要针对不同的成员
提供增加、删除和获取等管理和访问成员的方法,存在大量的冗余代码,系统维护较为困难;
(2) 由于系统没有提供抽象层,客户端代码必须有区别地对待充当容器的文件夹Folder和充当叶子
的ImageFile和TextFile,无法统一对它们进行处理;
(3) 系统的灵活性和可扩展性差,如果需要增加新的类型的叶子和容器都需要对原有代码进行修改,
例如如果需要在系统中增加一种新类型的视频文件VideoFile,则必须修改Folder类的源代码,
否则无法在文件夹中添加视频文件。
1、新建抽象文件类作为抽象构件
//抽象文件类:抽象构件
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();
}
2、新建图像文件类作为叶子构件
//图像文件类:叶子构件
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+"进行杀毒");
}
}
3、新建文本文件类
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+"进行杀毒");
}
}
4、新建文件夹类
import java.util.ArrayList;
public class Folder extends AbstractFile{
//用于存储AbstractFile类型的成员
private ArrayList 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 (Object obj : fileList) {
((AbstractFile)obj).killVirus();
}
}
}
5、客户端调用方式如下
public class Client {
public static void main(String[] args) {
//针对抽象构件编程
AbstractFile file1,file2,file3,file4,folder1,folder2,folder3;
folder1 = new Folder("工作资料");
folder2 = new Folder("图片文件");
folder3 = new Folder("文本文件");
file1 = new ImageFile("背景图.jpg");
file2 = new ImageFile("示例图.gif");
file3 = new TextFile("接口.doc");
file4 = new TextFile("示例.txt");
folder2.add(file1);
folder2.add(file2);
folder3.add(file3);
folder3.add(file4);
folder1.add(folder2);
folder1.add(folder3);
folder1.killVirus();
}
}
6、总结
通过引入组合模式,杀毒软件具有良好的可扩展性,在增加新的文件类型时,无须修改现有类库代码,
只需增加一个新的文件类作为AbstractFile类的子类即可,但是由于在AbstractFile中声明了大量用于管理和访问成员构件的方法,
例如add()、remove()等方法,我们不得不在新增的文件类中实现这些方法,提供对应的错误提示和异常处理。
所以代码还可简化将叶子构件的add()、remove()等方法的实现代码移至AbstractFile类中,由AbstractFile提供统一的默认实现。