【设计模式】结构型模式其三: 组合模式

367 阅读6分钟

结构型模式三:《组合模式》

什么是组合模式

组合模式是一种设计模式,它允许将对象组合成树形结构,以表示“部分-整体”的层次结构。组合模式使得客户端对单个对象组合对象的使用具有一致性,因为它们都具有相同的接口

image.png

分析:
在树形目录结构中,包含文件和文件夹两类不同的元素
在文件夹中可以包含文件,还可以继续包含子文件夹
在文件中不能再包含子文件或者子文件夹
文件夹: 容器(Container)
文件: 叶子(Leaf)

容器对象的某一个方法被调用时,将遍历整个树形结构寻找也包含这个方法的成员对象并调用执行,牵一而动百,其中使用了递归调用的机制来对整个结构进行处理

由于容器对象和叶子对象在功能上的区别,在使用这些对象的代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们,因为对于这些对象的区别对待将会使程序非常复杂.

组合模式的结构

image.png

组合模式包含以下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类中声明并实现这些方法

对于叶子对象,客户端不可能调用到这些方法

缺点是不够透明,客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件

模式优缺点

模式优点:

可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制

客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码

增加新的容器构件和叶子构件都很方便,符合开闭原则

为树形结构的面向对象实现提供了一种灵活的解决方案

模式缺点:

在增加新构件时很难对容器中的构件类型进行限制, 下面是解释。

组合模式在增加新构件时很难对容器中的构件类型进行限制,是因为组合模式强调了构件对象的统一接口,这使得容器对象无法区分不同类型的构件对象。