「聊设计模式」之组合模式(Composite)

🏆本文收录于《聊设计模式》专栏,专门攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎持续关注&&收藏&&订阅!


前言

在软件开发中,我们常常需要处理树形结构的问题。树形结构通常由树枝和叶子节点组成,每个树枝可以有多个子节点或叶子节点。而组合模式(Composite)就是一种处理树形数据结构的模式。

组合模式将对象组合成树形结构,以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

摘要

组合模式是一种结构型设计模式。可以将对象组合成树形结构,以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

在本文中,我们通过一个示例程序来阐释组合模式的实现方式、优缺点、使用场景及注意事项。

组合模式

概念

组合模式是一种结构型设计模式,允许将对象组合成树形结构来表示部分-整体的层次结构。它使客户端可以像处理单个对象一样处理对象的组合。组合模式使用递归算法从而可以处理复杂的层次结构,同时保持简单的代码结构。在组合模式中,有两种基本类型的对象:叶子节点和组合节点。叶子节点表示树形结构中的单个元素,而组合节点则表示一个包含多个子节点的组合元素。通过这种方式,组合模式可以帮助我们在层次结构中自由地添加、删除和修改对象,同时不会破坏整个树形结构的完整性。

结构

组合模式包含以下角色:

  • Component(抽象构件):它可以是接口或抽象类,为叶子构件和组合构件对象声明接口,实现所有类共有的接口默认行为,用于访问和管理 Component 的子部件。
  • Leaf(叶子构件):在组合中表示子节点对象,叶子节点没有子节点。
  • Composite(组合构件):定义组合中的枝干节点行为,用于存储子部件,在 Component 接口中实现与子部件有关的操作。

通过组合模式,我们可以将一个树形结构表示为一个对象,同时对于客户端而言,无论是访问树中的一个叶子节点还是一个枝干节点,都可以使用一致的方式进行操作。

下面是组合模式的 UML 类图:

优缺点

优点

  1. 可以清晰地定义复杂对象的层次结构;
  2. 客户端代码可以一致地处理单个对象和组合对象。

缺点

  1. 组合模式在增加新的构件时很难对系统的复杂度进行限制。

使用场景

组合模式通常在以下场景中使用:

  1. 表示对象的部分-整体层次结构;
  2. 让客户端统一处理单个对象和组合对象。

注意事项

  1. 不建议在组合模式中使用过多的类型判断(如 instanceof),否则会导致系统变得复杂;
  2. 程序中每个节点最好都有父节点的引用,方便操作。

示例程序

本文以操作系统文件系统为例,来阐释组合模式的实现方式。文件系统由文件夹(Folder)和文件(File)组成,其中文件夹可以包含多个文件夹或文件,而文件没有子节点。

我们先定义一个抽象的文件系统节点 Component

java 复制代码
package com.example.javaDesignPattern.composite;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 14:32
 */
public abstract class Component {
    protected String name;

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

    public abstract void add(Component c);

    public abstract void remove(Component c);

    public abstract void display();
}

该类定义了文件系统中节点的公共属性和方法,其中 add()remove()用于添加/移除子节点,display() 用于展示当前节点信息。

然后定义叶子节点 File:

java 复制代码
package com.example.javaDesignPattern.composite;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 14:33
 */
public class File extends Component {
    public File(String name) {
        super(name);
    }

    @Override
    public void add(Component c) {
        System.out.println("File cannot add any component");
    }

    @Override
    public void remove(Component c) {
        System.out.println("File cannot remove any component");
    }

    @Override
    public void display() {
        System.out.println("File Name: " + name);
    }
}

该类表示文件节点,它没有子节点,因此 add()remove() 操作无法实现。

最后定义组合节点 Folder

java 复制代码
public class Folder extends Component {
    private List<Component> children = new ArrayList<>();

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

    @Override
    public void add(Component c) {
        children.add(c);
    }

    @Override
    public void remove(Component c) {
        children.remove(c);
    }

    @Override
    public void display() {
        System.out.println("Folder Name: " + name);
        for (Component c : children) {
            c.display();
        }
    }
}

该类表示文件夹节点,它可以包含多个子节点,因此实现了 add()remove() 操作,并在 display() 中递归展示子节点信息。

我们可以使用如下方式测试程序:

java 复制代码
package com.example.javaDesignPattern.composite;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/19 14:34
 */
public class CompositeTest {
    public static void main(String[] args) {
        Component root = new Folder("root");
        Component folder1 = new Folder("folder1");
        Component folder2 = new Folder("folder2");
        Component folder3 = new Folder("folder3");

        Component file1 = new File("file1");
        Component file2 = new File("file2");
        Component file3 = new File("file3");

        folder1.add(file1);
        folder2.add(file2);
        folder3.add(file3);

        root.add(folder1);
        root.add(folder2);
        root.add(folder3);

        root.display();
    }
}

输出结果如下:

代码解读

这是一个演示组合模式的 Java 代码。组合模式是一种结构型模式,它将对象组合成树形结构以表示"部分-整体"的层次关系。组合模式使得用户对单个对象和组合对象的使用具有一致性。

在这个示例中,首先定义了一个 Component 接口,它包含了 add()remove()display() 方法,用于添加、移除和展示组件。然后定义了两个具体的组件类:FolderFile。其中 Folder 类具有一个 ArrayList 成员变量,用于存储它所包含的组件。

main() 方法中,首先创建了一个根节点 root,然后创建了三个文件夹和三个文件,并将文件添加到对应的文件夹中。最后将三个文件夹添加到根节点下,并调用根节点的 display() 方法,展示整个组合结构。

输出结果类似于,如下:

java 复制代码
root
---folder1
------file1
---folder2
------file2
---folder3
------file3

其中 - - - 表示缩进,表示嵌套的层次关系。

附录源码

如上涉及代码均已上传同步在 GitHub,提供给同学们参考性学习。

总结

本文介绍了组合模式的实现方式、优缺点、使用场景及注意事项,并通过一个文件系统示例程序进行了阐述。组合模式提供了一种处理树形结构数据的方式,它使得客户端代码可以一致地处理单个对象和组合对象。同时,我们也需要注意在实现过程中避免过多的类型判断,同时保证每个节点都有父节点的引用。

☀️建议/推荐你


如果想系统性的全面学习设计模式,建议小伙伴们直接毫无顾忌的关注这个专栏《聊设计模式》,无论你是想提升自己的编程技术,还是渴望更好地理解代码背后的设计思想,本专栏都会为你提供实用的知识和启发,帮助你更好地解决日常开发中的挑战,将代码变得更加优雅、灵活和可维护!

📣关于我


我是bug菌,CSDN | 掘金 | infoQ | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,华为云 | 阿里云| 腾讯云等社区优质创作者,全网粉丝合计15w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。

相关推荐
蓑 羽5 分钟前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode
叫我:松哥11 分钟前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
Reese_Cool13 分钟前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言
海里真的有鱼14 分钟前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
工业甲酰苯胺25 分钟前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
严文文-Chris38 分钟前
【设计模式-享元】
android·java·设计模式
Flying_Fish_roe1 小时前
浏览器的内存回收机制&监控内存泄漏
java·前端·ecmascript·es6
新知图书1 小时前
Rust编程的作用域与所有权
开发语言·后端·rust
c#上位机1 小时前
C#事件的用法
java·javascript·c#