16. 23种设计模式之<组合模式>

411 阅读6分钟

这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战

一. 什么是组合模式?

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

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

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

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

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

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

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

案例一: 文件结构

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

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

代码实现:

  1. 抽象的文件接口
package com.lxl.www.designPatterns.compositePattern.files;

/**
 * 抽象的文件接口
 */
public abstract class File {
    protected String name;

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

    public abstract void show();
}

  1. 具体文件 文本文件
package com.lxl.www.designPatterns.compositePattern.files;

public class TextFile extends File {
    public TextFile(String name) {
        super(name);
    }

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

图片文件

package com.lxl.www.designPatterns.compositePattern.files;

public class ImageFile extends File {
    public ImageFile(String name) {
        super(name);
    }

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

音频文件

package com.lxl.www.designPatterns.compositePattern.files;

public class AudioFile extends File {
    public AudioFile(String name) {
        super(name);
    }

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

视频文件

package com.lxl.www.designPatterns.compositePattern.files;

public class VideoFile extends File {
    public VideoFile(String name) {
        super(name);
    }

    @Override
    public void show() {
        System.out.println("这是视频文件----展示");
    }
}
  1. 文件夹
package com.lxl.www.designPatterns.compositePattern.files;

import java.util.ArrayList;
import java.util.List;

public class Folder extends File{

    List<File> files = new ArrayList<File>();

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

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

    public void delFile(File file) {
        files.remove(file);
    }

    @Override
    public void show() {
        System.out.println("这是一个" + this.name + "----展示");
        for(File file: this.files) {
            file.show();
        }
    }
}

  1. 客户端client
package com.lxl.www.designPatterns.compositePattern.files;

public class Client {
    public static void main(String[] args) {
        File text1 = new TextFile("txt文件");
        File image1 = new ImageFile("图片类型文件");
        File audio1 = new AudioFile("音频文件");
        File video1 = new VideoFile("视频文件");


        Folder folder = new Folder("一级文件夹");
        folder.addFile(text1);
        folder.addFile(image1);
        folder.addFile(audio1);
        folder.addFile(video1);

        Folder folder2 = new Folder("二级文件夹");
        folder.addFile(folder2);

        File text2 = new TextFile("txt文件2");
        File image2 = new ImageFile("图片类型文件2");
        File audio2 = new AudioFile("音频文件2");
        File video2 = new VideoFile("视频文件2");

        folder2.addFile(text2);
        folder2.addFile(image2);
        folder2.addFile(audio2);
        folder2.addFile(video2);

        folder.show();
    }
}

  1. 运行结果 最终, 我们运行程序的结果是

这是一个一级文件夹----展示
这是文本文件----展示
这是图片文件----展示
这是音频文件----展示
这是视频文件----展示
这是一个二级文件夹----展示
这是文本文件----展示
这是图片文件----展示
这是音频文件----展示
这是视频文件----展示

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

案例二: 公司组织架构

先来看uml图:

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

接下来看看代码实现 第一步: 部门抽象接口

/**
 * 部门
 */
public interface Department {

    /**
     * 执行工作
     */
    void show();
}

第二步: 叶子部门, 下面没有其他任何部门

package com.lxl.www.designPatterns.compositePattern.company;

/**
 * 叶子部门, 也就是部门下面没有其他部门了
 */
public class LeafDepartment implements Department{
    /**
     * 部门名称
     */
    private String name;
    public LeafDepartment(String name) {
        this.name = name;
    }

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

第三步: 聚合部门, 也就是下面还有子部门

package com.lxl.www.designPatterns.compositePattern.company;

import java.util.ArrayList;
import java.util.List;

/**
 * 聚合部门--也就是旗下还有其他部门
 */
public class AggregateDepartmen implements Department{

    /** 部门名称 */
    private String name;

    private List<Department> departments = new ArrayList<>();

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

    public void addDepartment(Department department) {
        departments.add(department);
    }

    public void delDepartment(Department department) {
        departments.remove(department);
    }


    @Override
    public void show() {
        System.out.println(this.name + ", 旗下还有 " + departments.size() + " 个子部门");
        for (Department department: departments) {
            department.show();
        }
    }
}

第四步: 客户端调用

package com.lxl.www.designPatterns.compositePattern.company;

/**
 * 公司部门客户端
 */
public class Client {
    public static void main(String[] args) {
        AggregateDepartmen company = new AggregateDepartmen("公司");

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

        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();
    }
}

第五步:运行结果

公司, 旗下还有 4 个子部门
法务部
行政部
人事部
技术部, 旗下还有 4 个子部门
Java事业部
C++事业部
GO事业部
大前端事业部

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

三. 组合模式的使用场景

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

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

四. 组合模式的优缺点

优点

  1. 让客户端的操作不在区分是组合对象还是叶子对象,采用统一的方式操作
  2. 高层模块调用简单。
  3. 节点自由增加

缺点

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

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