11-组合模式

一、什么是组合模式?

组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分到底是容器对象还是叶子对象,可以对他们进行一致的处理。

组合模式通过组合多个对象形成树形结构来表示“整体-部分”的结构层次。

组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关系。同时它也模糊了**简单元素(叶子对象)和复杂元素(容器对象)**的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客户程序能够与复杂元素的内部结构解耦。

到底是什么意思呢? 来看看下面的图片:

组合模式的示例

现在有文件夹, 文件夹下可以有文件和文件夹. 这种递归调用, 我们拆分来看, 文件可以看做是叶子节点, 文件夹可以看做是容器. 文件夹容器下还有文件和文件夹. 使用组合模式来实现他们之间的关系.

组合模式的关键是: 设计一个抽象的组合类, 让它可以代表组合对象和叶子对象。这样的好处是,客户端不需要区分到底是组合对象还是叶子对象了,只需要全部当成组合对象来处理即可。

二、组合模式的结构和案例

1. 案例一: 文件结构

下面就来看看如何实现文件结构. 先来看看UML图:

组合模式-文件结构

首先定义一个抽象类 File, 然后单个的具体文件和文件夹都实现了抽象文件接口, 在文件夹接口里面还有一个存放抽象文件的集合. 实现了文件和文件夹的组合.

代码实现:

// 抽象类
public abstract class File {

    public String name;

    public File(String name) {
        this.name = name;
    }

    /**
     * 文件打印
     */
    public abstract void show();
}


// 文本文件
public class TextFile extends File { // 继承File
    public TextFile(String name) {
        super(name);
    }

    @Override
    public void show() {
        System.out.println("文本文件展示-----------");
    }
}


// 图像文件
public class ImageFile extends File { // 继承File
    public ImageFile(String name) {
        super(name);
    }

    @Override
    public void show() {
        System.out.println("图片文件展示-----------");
    }
}


// 音频文件
public class AudioFile extends File { // 继承File
    public AudioFile(String name) {
        super(name);
    }

    @Override
    public void show() {
        System.out.println("图片文件展示-----------");
    }
}


// 视频文件
public class VideoFile extends File { // 继承File
    public VideoFile(String name) {
        super(name);
    }

    @Override
    public void show() {
        System.out.println("视频文件展示-----------");
    }
}


public class Folder extends File { // 继承File

    // 组合File
    private List<File> files = new ArrayList<>();

    public Folder(String name) {
        super(name);
    }

    public void add(File file) {
        files.add(file);
    }

    @Override
    public void show() { // 递归调用 show() 方法
        System.out.println("这是 " + this.name + " 的show方法");
        for (File file : files) {
            file.show();
        }
    }
}


// 测试类
public class FileClient {
    public static void main(String[] args) {
        File textFile1 = new TextFile("text1文件");
        File imageFile1 = new ImageFile("image1文件");
        File audioFile1 = new AudioFile("audio1文件");
        File videoFile1 = new VideoFile("video1文件");
        Folder folder1 = new Folder("folder1");
        folder1.add(textFile1);
        folder1.add(imageFile1);
        folder1.add(audioFile1);
        folder1.add(videoFile1);

        File textFile2 = new TextFile("text2文件");
        File imageFile2 = new ImageFile("image2文件");
        Folder folder2 = new Folder("folder2");
        folder2.add(textFile2);
        folder2.add(imageFile2);

        // 将文件夹2 添加到文件夹1中
        folder1.add(folder2);

        folder1.show();
    }
}

运行结果:

这是 folder1 的show方法
文本文件展示-----------
图片文件展示-----------
图片文件展示-----------
视频文件展示-----------
这是 folder2 的show方法
文本文件展示-----------
图片文件展示-----------

最终展示的文件效果如下:

文件组合结果示意

2. 案例二: 公司组织架构

先来看uml图:

组合模式-部门

这里面通常公司下面会下设很多部门, 部门下面在设置子部门, 其组织架构如下图:

组织架构图

代码实现

// 第一步: 部门抽象接口
public interface Department {

    // 打印部门信息
    void show();
}


// 第二步: 叶子部门, 下面没有其他任何部门
public class LeafDepartment implements Department {
    // 部门名称
    public String name;

    public LeafDepartment(String name) {
        this.name = name;
    }

    @Override
    public void show() {
        System.out.println(this.name);
    }
}


// 第三步: 聚合部门, 也就是下面还有子部门
public class AggregateDepartment implements Department {
    private String name;

    // 聚合下属部门
    List<Department> departmentList = new ArrayList<>();

    public AggregateDepartment(String name) {
        this.name = name;
    }

    // 添加部门
    public void addDepartment(Department department) {
        departmentList.add(department);
    }

    @Override
    public void show() {
        System.out.println("当前部门为:" + this.name + " 下属部门为:");
        for (Department department : departmentList) {
            department.show();
        }
    }
}


// 第四步: 客户端调用
public class DepartmentClient {
    public static void main(String[] args) {
        AggregateDepartment company = new AggregateDepartment("公司");

        Department fawu = new LeafDepartment("法务部");
        Department xingzheng = new LeafDepartment("行政部");
        Department renshi = new LeafDepartment("人事部");
        AggregateDepartment jishu = new AggregateDepartment("技术部");

        Department java = new LeafDepartment("Java事业部");
        Department c = new LeafDepartment("C++事业部");
        Department go = new LeafDepartment("GO事业部");
        Department front = new LeafDepartment("大前端事业部");

        company.addDepartment(fawu);
        company.addDepartment(xingzheng);
        company.addDepartment(renshi);
        company.addDepartment(jishu);

        jishu.addDepartment(java);
        jishu.addDepartment(c);
        jishu.addDepartment(go);
        jishu.addDepartment(front);

        company.show();
    }
}

运行结果:

当前部门为:公司 下属部门为:
法务部
行政部
人事部
当前部门为:技术部 下属部门为:
Java事业部
C++事业部
GO事业部
大前端事业部

通过组合模式, 我们就将一个公司的组织架构画出来了, 即使是更复杂的架构, 使用这种方式也可实现.

三、组合模式的使用场景

组合模式在我们日常生活中非常常见, 比如: 公司组织架构中架构关系. 总公司和分公司的关系, 下面分析它适用的以下应用场景。

  • 在需要表示一个对象整体与部分的层次结构的场合。
  • 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。

四、组合模式的总结

  • 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
  • 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
  • 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的 树形结构
  • 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的 树形结构
  • 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式

五、组合模式对设计模式六大原则的运用

  • 单一职责原则: 一个接口只做一件事
  • 里式替换原则: 父类出现的地方都可以使用子类替换, 这里父类方法都是接口
  • 接口隔离原则: 最小接口, 不要出现胖接口
  • 依赖倒置原则: 面向接口编程,而不是面向具体编程
  • 迪米特法则: 和最少的对象产生关联
  • 开闭原则: 对修改开放, 对扩展关闭

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 george_95@126.com