5/24 设计模式之组合模式 Composite Pattern

1,249 阅读3分钟

类别:结构型设计模式

目的:表示一组对象,这组对象的用法和单个对象的用法一致

完整代码参考:1drv.ms/u/s!AquRvPz…

典型场景

处理电脑上的文件和文件夹

基本事实

  1. 文件和文件夹可以有多个
  2. 文件和文件夹有多个共同的操作,比如:删除、移动、复制等

这里拿对多个文件夹和文件执行删除操作举例,对应mac下右键Move to Trash按钮

可以看到,可以选中多个文件和文件夹执行删除、复制、移动、获取属性信息等操作

硬编码

这里拿在业务逻辑中删除2个文件和1个文件夹举例

文件File和文件夹Folder类原型

public class File {
    private String path;

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

    public void delete() {
        System.out.println("file: " + path + " was deleted.\n");
    }
}

public class Folder {
    private String path;

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

    public void delete() {
        System.out.println("folder: " + path + " was deleted.\n");
    }
}

为了能够同时删除多个文件/文件夹、很容易写出下面的聚合代码

聚合删除文件和文件夹Group类

public class Group {
    private List<Object> items = new ArrayList<>();

    public void add(Object object) {
        items.add(object);
    }

    public void delete() {
        for (Object object : items) {
            if (object instanceof File) {
                ((File) object).delete();
            } else if (object instanceof Folder) {
                ((Folder) object).delete();
            }
        }
    }
}

实际使用举例如下

public class Main {
    public static void main(String[] args) {
        var file1 = new File("1.txt");
        var file2 = new File("2.txt");
        var folder1 = new Folder("/tmp/folder1");

        var group = new Group();
        group.add(file1);
        group.add(file2);
        group.add(folder1);

        businessLogic(group);
    }

    public static void businessLogic(Group group) {
        group.delete();
    }
}

核心问题

文件和文件夹的删除逻辑和类型耦合了,参考Group::delete()方法、随着类型的细分,比如文件类型继续细分为压缩文件和图片文件,那么delete()中对类型的耦合会越来越多

如果还存在其它批量操作,比如批量获取文件/文件夹信息,比如Group::getInfo()方法,name这种耦合代码还将重复一次

解决方式:组合模式,利用组合模式重构代码结构,使得文件和文件夹、分组都实现一个公有操作的接口,比如

模式实现

public interface Component {
    void delete();
}

结构更改如下

  1. public class File implements Component
  2. public class Folder implements Component
  3. public class Group implements Component

然后在Group::delete()时就无须关心类型了,所有的Component类型都有delete方法了,实现变更为:

public void delete() {
    for (Component component : items) {
        component.delete();
    }
}

可以参考完整代码

执行效果如下:

可以看到文件和文件夹的删除操作都被调用到了,分组Group也可以像文件和文件夹一样被add进别的分组了,达到了组合模式,一个组合可以被当成独立个体使用的目的,参考下面的group2:

public static void main(String[] args) {
    var file1 = new File("1.txt");
    var file2 = new File("2.txt");
    var folder1 = new Folder("/tmp/folder1");

    var group = new Group();
    group.add(file1);
    group.add(file2);
    group.add(folder1);

    var group2 = new Group();
    group2.add(group);
    group2.add(new File("3.txt"));

    businessLogic(group2);
}

public static void businessLogic(Group group) {
    group.delete();
}

UML

-w672

一些注意的点

需要使用接口技术使得不同类型的对象保持相同的行为

为什么组合模式更好

  1. 避免大量的类型检查逻辑
  2. 避免重复的类型检查逻辑

参考资料

  1. www.geeksforgeeks.org/composite-d…