组合模式(Composite Pattern)

发布时间 2023-05-21 16:02:22作者: 街酒

一、模式动机

处理树型结构

组合模式(Composite Pattern)关注那些存在叶子构件和容器构件的结构以及它们的组织形式叶子构件中不能包含成员对象,而容器构件中可以包含成员对象,这些成员对象可能是叶子构件对象,也可能是容器构件对象。这些对象可以构成一个树形结构组合模式用面向对象的方式来处理树形结构,它为叶子构件和容器构件提供了一个公共的抽象构件类,客户端可以针对该抽象类进行处理,而无需关心所操作的是哪种类型的对象。


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

如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象?

  • 对于树形结构.当容器对象(如文件夹)的某一个方法被调川时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行,牵一而动百,其中使用了递归调用的机制来对整个结构进行处理由于容器对象和叶子对象在功能上有区别,在使用这些对象的客户端代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们。因为对于这些对象的区别对待将会使得程序非常复杂。
  • 组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,它描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象。

二、模式定义

  • 组合多个对象形成树形结构以表示“部分-整体”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
  • 对象结构型模式
  • 将对象组织到树形结构中,可以用来描述整体与部分的关系

三、模式结构

image

四、案例实现

案例背景

文件系统,文件夹为容器,文件为叶子

案例结构

image

代码实现

抽象构件

public interface AbstractFile {

    void add(AbstractFile abstractFile);
    void remove(AbstractFile abstractFile);
    void display();
    AbstractFile getFile(int i);

}

叶子构件
图片文件、视频文件、文本文件

public class ImageFile implements AbstractFile{

    private String fileName;

    public ImageFile(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void add(AbstractFile abstractFile) {
        System.out.println("无法执行!!");
    }

    @Override
    public void remove(AbstractFile abstractFile) {
        System.out.println("无法执行!!");
    }

    @Override
    public void display() {
        System.out.println("——正在打开"+fileName+"文件 ...... 浏览"+fileName);
    }

    @Override
    public AbstractFile getFile(int i) {
        System.out.println("无法执行!!");
        return null;
    }

}
public class VideoFile implements AbstractFile{

    private String fileName;

    public String getFileName() {
        return fileName;
    }

    public VideoFile(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void add(AbstractFile abstractFile) {
        System.out.println("无法执行!!");
    }

    @Override
    public void remove(AbstractFile abstractFile) {
        System.out.println("无法执行!!");
    }

    @Override
    public void display() {
        System.out.println("——正在打开"+fileName+"文件 ...... 播放"+fileName);
    }

    @Override
    public AbstractFile getFile(int i) {
        System.out.println("无法执行!!");
        return null;
    }

}
public class TextFile implements AbstractFile{

    private String fileName;

    public TextFile(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void add(AbstractFile abstractFile) {
        System.out.println("无法执行!!");
    }

    @Override
    public void remove(AbstractFile abstractFile) {
        System.out.println("无法执行!!");
    }

    @Override
    public void display() {
        System.out.println("——正在打开"+fileName+"文件 ...... 查看"+fileName);
    }

    @Override
    public AbstractFile getFile(int i) {
        System.out.println("无法执行!!");
        return null;
    }

}

容器构件
文件夹

public class Folder implements AbstractFile{

    private String fileName;
    private ArrayList<AbstractFile> filelist = new ArrayList<>();

    public Folder(String fileName) {
        this.fileName = fileName;
    }

    @Override
    public void add(AbstractFile abstractFile) {
        filelist.add(abstractFile);
    }

    @Override
    public void remove(AbstractFile abstractFile) {
        filelist.remove(abstractFile);
    }

    @Override
    public void display() {
        System.out.println("——正在打开"+fileName+"文件夹");
        for (Object obj:filelist) {
            ((AbstractFile)obj).display();
        }
    }

    @Override
    public AbstractFile getFile(int i) {
        return (AbstractFile) filelist.get(i);
    }

}

实现结果

image

案例分析

文件不能包含子文件,子文件管理和访问方法需要提供异常处理或错误提示。
文件夹具体业务方法的实现,将递归调用成员构件的业务方法

五、模式分析

  • 叶子结点不能包含子构件,子构件管理和访问方法需要提供异常处理或错误提示。
  • 容器构件具体业务方法的实现,将递归调用成员构件的业务方法

六、总结

模式优点

  • 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
  • 增加新的容器构件和叶子构件都很方便,符合开闭原则
  • 为树形结构的面向对象实现提供了一种灵活的解决方案

模式缺点

  • 设计更加抽象,对象的业务规则如果很复杂,实现组合模式具有较大难度。而且不是所有的方法都与叶子对象子类都有关联。
  • 在增加新构件时很难对容器中的构件类型进行限制

使用情形

  • 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们
  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构
  • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型

扩展

透明组合模式

  • 抽象构件Component中声明了所有用于管理成员对象的方法,包括add()、remove(),以及getChild()等方法
  • 优点是确保所有的构件类都有相同的接口;
  • 在客户端看来,叶子对象与容器对象所提供的方法是一致的,客户端可以一致地对待所有的对象
  • 缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的。

安全组合模式

  • 抽象构件Component中没有声明任何用于管理成员对象的方法,而是在Composite类中声明并实现这些方法
  • 优点是安全
    • 对于叶子对象,客户端不可能调用到这些方法
  • 缺点是不够透明,客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件
  • 使用频率更高