设计模式-组合模式(Composite Pattern)

news2024/11/28 2:44:33

1. 概念

  • 组合模式是一种结构型设计模式,它允许将对象组合成树状的层次结构,用来表示“整体-部分”的关系。

2. 原理结构图

原理图
在这里插入图片描述

  • 抽象角色(Component):这是组合模式的核心,它定义了树叶和树枝构件的公共接口,并可能提供一些默认行为。在透明式的组合模式中,它还声明了访问和管理子类的接口;而在安全式的组合模式中,这些管理工作由树枝构件完成。
  • 树叶角色(Leaf):这个角色代表了组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。简单来说,树叶构件是基本的对象,没有进一步分解的部分。
  • 树枝角色(Composite):这个角色是组合中的分支节点对象,它有子节点,同样用于继承和实现抽象构件。树枝构件的主要作用是存储和管理子部件,通常包含添加、删除、获取子节点的方法。

透明组合模式在这里插入图片描述

  • 透明组合模式的特点是Leaf 和 Composite 具有相同的接口。因此,无论客户端处理的是 Leaf 还是 Composite,都可以使用相同的接口。这使得客户端的操作更加简单和直观。然而,透明组合模式的一个潜在问题是,对于叶子节点来说,某些操作(如添加或删除子节点)是无意义的,这可能在运行时引发错误,除非有适当的错误处理机制。

安全组合模式
在这里插入图片描述

  • 安全组合模式的主要特点是,它将管理子构件的方法移到树枝构件中,使得抽象构件和树叶构件没有管理子对象的方法,从而避免了潜在的安全性问题。这种设计使得客户端在处理不同角色时能够明确区分,但对于客户端来说,可能需要针对不同类型的对象进行不同的操作,因此不够透明。

3. 代码示例

3.1 示例1:透明组合模式
  • 一个公司的组织结构,其中公司是一个复合对象,包含多个部门,而部门既可以包含其他部门(例如子部门),也可以包含员工(叶子节点)。
interface Component {  
    void operation();  
    void add(Component component);  
    void remove(Component component);  
    Component getChild(int index);  
    int getChildCount();  
    boolean isComposite();  
}
// 叶子节点:员工  
class Leaf implements Component {  
    private String name;  
  
    public Leaf(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void operation() {  
        System.out.println("Employee " + name + " is working.");  
    }  
  
    @Override  
    public void add(Component component) {  
        throw new UnsupportedOperationException("Cannot add to a leaf.");  
    }  
  
    @Override  
    public void remove(Component component) {  
        throw new UnsupportedOperationException("Cannot remove from a leaf.");  
    }  
  
    @Override  
    public Component getChild(int index) {  
        throw new UnsupportedOperationException("Leaf has no children.");  
    }  
  
    @Override  
    public int getChildCount() {  
        return 0;  
    }  
  
    @Override  
    public boolean isComposite() {  
        return false;  
    }  
}  
  
// 复合节点:部门  
class Composite implements Component {  
    private List<Component> children = new ArrayList<>();  
    private String name;  
  
    public Composite(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void operation() {  
        System.out.println("Department " + name + " is managing its resources.");  
        for (Component child : children) {  
            child.operation();  
        }  
    }  
  
    @Override  
    public void add(Component component) {  
        children.add(component);  
    }  
  
    @Override  
    public void remove(Component component) {  
        children.remove(component);  
    }  
  
    @Override  
    public Component getChild(int index) {  
        return children.get(index);  
    }  
  
    @Override  
    public int getChildCount() {  
        return children.size();  
    }  
  
    @Override  
    public boolean isComposite() {  
        return true;  
    }  
}

public class CompanyStructureDemo {  
    public static void main(String[] args) {  
        // 创建部门(复合节点)和员工(叶子节点)  
        Component hr = new Composite("HR Department");  
        Component it = new Composite("IT Department");  
        Component employee1 = new Leaf("John Doe");  
        Component employee2 = new Leaf("Jane Smith");  
        Component subDepartment = new Composite("Sub IT Department");  
        Component employee3 = new Leaf("Bob Johnson");  
  
        // 将员工添加到部门中  
        hr.add(employee1);  
        hr.add(employee2);  
  
        // 创建子部门,并将员工添加到子部门中  
        subDepartment.add(employee3);  
  
        // 将子部门添加到IT部门中  
        it.add(subDepartment);  
  
        // 执行操作  
        hr.operation();  
        it.operation();  
  
        // 移除操作(如果需要)  
        // hr.remove(employee1);  
  
        // 访问子节点(如果需要)  
        // Component child = hr.getChild(0);  
        // child.operation();  
    }  
}
  • 输出
Department HR Department is managing its resources.
Employee John Doe is working.
Employee Jane Smith is working.
Department IT Department is managing its resources.
Department Sub IT Department is managing its resources.
Employee Bob Johnson is working.
  • 这个案例展示了组织结构,其中部门可以包含其他部门或员工,而员工不能包含其他组件。通过这种方式,客户端可以一致地处理单个员工和整个部门,无需关心它们的具体类型。

3.2 示例2:安全组合模式
  • 文件系统案例
interface FileSystemElement {  
    void print(); // 打印文件系统元素  
}

class File implements FileSystemElement {  
    private String name;  
    private long size;  
  
    public File(String name, long size) {  
        this.name = name;  
        this.size = size;  
    }  
  
    @Override  
    public void print() {  
        System.out.println("文件: " + name + ", 大小: " + size + " 字节");  
    }  
}

class Directory implements FileSystemElement {  
    private String name;  
    private List<FileSystemElement> children = new ArrayList<>();  
  
    public Directory(String name) {  
        this.name = name;  
    }  
  
    @Override  
    public void print() {  
        System.out.println("目录: " + name);  
        for (FileSystemElement child : children) {  
            child.print(); // 递归打印子文件和子目录  
        }  
    }  
  
    // 安全组合模式特有的方法,用于添加子文件和子目录  
    public void add(FileSystemElement fileSystemElement) {  
        children.add(fileSystemElement);  
    }  
  
    // 安全组合模式特有的方法,用于移除子文件和子目录  
    public void remove(FileSystemElement fileSystemElement) {  
        children.remove(fileSystemElement);  
    }  
  
    // 隐藏子文件和子目录的访问,这是安全组合模式的关键点  
    // 这里没有提供直接访问子文件和子目录的方法  
}

public class FileSystemDemo {  
    public static void main(String[] args) {  
        // 创建文件  
        FileSystemElement file1 = new File("example.txt", 1024);  
        FileSystemElement file2 = new File("report.pdf", 5120);  
  
        // 创建目录  
        Directory docsDir = new Directory("docs");  
        docsDir.add(file1);  
  
        Directory imagesDir = new Directory("images");  
        imagesDir.add(new File("photo.jpg", 20480));  
  
        // 创建根目录  
        Directory rootDir = new Directory("/");  
        rootDir.add(docsDir);  
        rootDir.add(imagesDir);  
        rootDir.add(file2);  
  
        // 打印整个文件系统  
        rootDir.print();  
  
        // 客户端不能直接操作子文件和子目录,只能通过目录系统来完成  
        // 例如,添加文件需要首先找到对应的目录  
        // 然后通过目录提供的方法来添加  
        // Directory newDir = new Directory("newDir");  
        // rootDir.add(newDir);  
  
        // 同样,移除文件或目录也需要通过文件系统来完成  
        // rootDir.remove(docsDir);  
    }  
}
  • 将看到如下输出:
目录: /  
目录: docs  
文件: example.txt, 大小: 1024 字节  
目录: images  
文件: photo.jpg, 大小: 20480 字节  
文件: report.pdf, 大小: 5120 字节
  • 在这个安全组合模式的实现中,客户端可以打印整个文件系统或者子目录,但不能直接操作子文件和子目录。对于文件和目录的添加、删除和修改,客户端需要通过目录提供的 add 和 remove 方法来执行。这样,文件和目录的管理被封装在目录内部,提高了安全性并简化了客户端的使用。安全组合模式适用于那些需要限制对子对象直接访问的场景,如本例中的文件系统。

4. 优缺点

  • 主要作用
    • 将对象组合成树形结构以表示“部分-整体”的层次关系,它让客户端可以一致地对待单个对象和组合对象。
  • 优点
    • 定义层次结构:它允许你清晰地定义分层次的复杂对象,并表示对象的全部或部分层次。
    • 忽略层次差异:客户端可以忽略层次之间的差异,方便对整个层次结构进行控制。
    • 简化客户端代码:由于组合模式提供了统一的接口来处理单个对象和组合对象,因此可以减少客户端的代码复杂度。
    • 统一访问方式:用户可以通过一致的方式访问单个对象和组合对象,这使得接口对外显得透明,简化了用户的操作。
  • 缺点
    • 设计复杂性增加:组合模式使得设计变得更加复杂,因为需要处理不同层次的组件,包括叶子节点和容器节点。这要求开发者对整体结构有深入的理解,以正确实现和管理这些组件。
    • 管理困难:在组合模式中,容器对象可以包含其他容器对象,这种递归结构可能导致难以管理和维护,特别是在大型系统中。同时,对于组件的添加、删除和修改操作也可能变得复杂。
    • 性能开销:由于组合模式中的对象通常以树形结构存在,因此在进行一些操作时(如遍历树),可能会引入额外的性能开销,特别是在处理大型树结构时。
    • 客户端需要了解对象结构:虽然组合模式提供了统一的接口来操作组件,但客户端仍然需要了解组件的结构和类型(例如,区分叶子节点和容器节点),这可能会增加客户端代码的复杂性。

5. 应用场景

5.1 主要包括以下几个方面
  1. 树状数据结构:任何需要以树状结构组织数据的场景都可以使用组合模式,例如组织架构、目录结构等。

5.2 实际应用
  1. 文件系统和目录管理:文件系统可以被视为一个树形结构,其中目录作为容器,文件和子目录作为内容。使用组合模式,可以方便地创建、删除、移动和复制文件或目录,实现统一的文件和目录管理。
  2. 组织架构和部门管理:在一个企业或机构中,通常存在多个部门和员工,形成一定的层次结构。使用组合模式,可以构建灵活的组织架构模型,实现部门、岗位和员工的统一管理和操作,如计算总工资、获取某个部门下的所有员工等。
  3. 菜单和菜单项管理:在图形界面中,菜单通常包含多个菜单项,菜单项可以是子菜单或其他操作项。组合模式可以用于构建菜单的树形结构,实现菜单和菜单项的添加、删除、遍历等统一操作。

6. JDK中的使用

  • 在集合框架中,List、Set和Map等接口表示不同类型的集合,而它们的实现类(如ArrayList、HashSet、HashMap等)则提供了具体的实现。这些接口和类之间的关系形成了树形结构,其中接口作为抽象构件,而实现类作为具体的叶子构件或容器构件。通过这种结构,用户可以统一地使用这些集合类,而无需关心它们的具体实现细节。

7. 注意事项

  • 抽象层次的一致性:确保客户端对单个对象和组合对象的使用具有一致性。这意味着,无论是操作单个对象还是操作组合对象,客户端调用的接口应该是一样的。这有助于简化客户端代码,并增强系统的灵活性和可扩展性。
  • 递归处理:由于组合模式涉及树形结构,因此在处理组合对象时,通常需要递归地遍历整个树结构。在编写递归算法时,要特别注意避免无限递归和栈溢出等问题。
  • 安全性与完整性:在添加、删除或修改组合对象中的成员时,要确保操作的安全性和数据的完整性。例如,在删除成员时,要确保不会破坏树形结构的完整性;在添加成员时,要确保新成员与现有成员之间的关系正确无误。
  • 性能考虑:组合模式在处理大型树形结构时可能会带来性能问题。由于需要递归遍历整个树结构,如果树很大,那么处理时间可能会很长。因此,在设计系统时,要充分考虑性能因素,并考虑使用缓存、优化算法等方式来提高性能。
  • 扩展性:在设计组合模式时,要考虑到未来的扩展性。例如,如果将来需要添加新的操作或新的对象类型,系统应该能够灵活地适应这些变化。这可以通过使用接口、抽象类等方式来实现。
  • 封装性:组合模式中的内部实现细节应该被封装起来,以避免客户端直接访问和操作内部对象。这样可以提高系统的安全性和稳定性,并降低客户端代码的复杂性。
  • 避免过度使用:虽然组合模式具有很多优点,但并不意味着在所有情况下都应该使用它。过度使用组合模式可能会导致系统变得复杂且难以维护。因此,在决定是否使用组合模式时,要充分考虑实际需求和系统的特点。

8. 生成器模式 VS 组合模式 VS 装饰器模式

模式目的模式架构主要角色应用场景
建造者模式分步构建复杂对象指挥者,生成器构建具有复杂逻辑的对象
组合模式表示具有层次结构的对象组合类和叶子节点树形结构和递归结构
装饰器模式动态添加新功能抽象组件和装饰器功能组合和扩展

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1587137.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多模态 ——LLaVA 集成先进图像理解与自然语言交互GPT-4的大模型

概述 提出了一种大型模型 LLaVA&#xff0c;它使用 GPT-4 生成多模态语言图像指令跟随数据&#xff0c;并利用该数据将视觉和语言理解融为一体。初步实验表明&#xff0c;LLaVA 展示了出色的多模态聊天能力&#xff0c;在合成多模态指令上的表现优于 GPT-4。 在科学质量保证中…

Web程序设计-实验01 HTML与CSS基础

【实验主题】 影视详情页设计 【实验任务】 1、浏览并分析多个影视详情页面&#xff08;详见参考资源&#xff0c;建议自行搜索更多影视网站&#xff09;的主要元素构成和版面设计&#xff0c;借鉴并构思预期效果。 2、新建 index.html文件&#xff0c;合理运用HTML标记编写…

基于GSP工具箱的NILM算法matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于GSP工具箱的NILM算法matlab仿真。GSP是图形信号处理的缩写&#xff0c;GSP非常适合对未知数据进行分类&#xff0c;尤其是当训练数据非常短时。GSPBox的基本理论是谱图论和…

SEDEX验厂是什么?

SEDEX验厂是一种审核流程&#xff0c;其主要目的是评估工厂在劳工标准、环境管理、健康与安全以及管理体系等方面的合规性。以下是关于SEDEX验厂的一些关键信息&#xff1a; SEDEX验厂审核标准主要包括以下几个方面&#xff1a; 劳工标准和劳动法规&#xff1a;工厂必须遵守当…

Redis中的集群(七)

集群 ASK错误 ASKING命令 ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识&#xff0c;以下是该命令的伪代码实现: def ASKING(): # 打开标识 client.flags | REDIS_ASKING# 向客户端返回OK回复 reply("OK")在一般情况下&#xff0c;如果客户…

谷歌官方力作——CodeGemma代码语言模型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

开发日志2024-04-11

开发日志2024/04/11 1、会员/普通用户预约完成后&#xff0c;技师对应的积分添加预约完成的项目价格添加到统计表的业绩字段中&#xff0c;同时对应的服务次数字段1 实现代码&#xff1a; 前端 shHandler(){this.$confirm(确定操作?, "提示", {confirmButtonText…

基于模型预测算法的含储能微网双层能量管理模型

基于模型预测算法的含储能微网双层能量管理模型 文章目录 基于模型预测算法的含储能微网双层能量管理模型一、项目介绍二、源程序下载 一、项目介绍 代码主要做的是一个微网双层优化调度模型&#xff0c;微网聚合单元包括风电、光伏、储能以及超级电容器&#xff0c;在微网的运…

ELFK (Filebeat+ELK)日志分析系统

一. 相关介绍 Filebeat&#xff1a;轻量级的开源日志文件数据搜集器。通常在需要采集数据的客户端安装 Filebeat&#xff0c;并指定目录与日志格式&#xff0c;Filebeat 就能快速收集数据&#xff0c;并发送给 logstash 进或是直接发给 Elasticsearch 存储&#xff0c;性能上相…

Maven、redis、javaJDK环境配置及安装

一、Maven下载配置 Maven下载地址 下载完成完成配置环境变量 新建系统变量 MAVEN_HOME 地址 设置MAVEN… mvn -v 检测成功 二 、redis安装 下载地址 在安装目录cmd输入redis-server --version检测版本号 三、JAVA配置 设置JAVA… 测试

【Vue3语法单文件——自用】

1. Vue3基础语法 <script setup> import { ref,computed } from vue// 定义响应式的变量 const count ref(0) const author ref({name: John Doe,books: [Vue 2 - Advanced Guide,Vue 3 - Basic Guide,Vue 4 - The Mystery] }) //定义props const props defineProps(…

贪心算法|56.合并区间

力扣题目链接 class Solution { public:vector<vector<int>> merge(vector<vector<int>>& intervals) {vector<vector<int>> result;if (intervals.size() 0) return result; // 区间集合为空直接返回// 排序的参数使用了lambda表达…

Mongodb前后端整合篇

一、前端篇 1.1mongoose介绍 Mongoose 是一个对象文档模型库&#xff0c;官网 http://www.mongoosejs.net/ 方便使用代码操作 mongodb 数据库pnpm i mongoose5.13.15 1.2初步使用 import mongoose from mongoose; //设置 strictQuery 为 true mongoose.set(strictQuery, true…

ubuntu或类Debian获取某些包的离线版本-包括依赖(还有一些意想不到的用途,哈哈)

前言 偶尔能碰到很特殊的情况。网址白名单&#xff0c;纯内网&#xff0c;超多依赖及一些很难描述的场景。 比如一些少见的发行版缺少某些包。这时候可以找一台类似的系统环境来下载离线包及 其依赖包&#xff0c;然后转移到内网进行安装。如果是网址白名单&#xff0c;或者纯内…

为什么需要网络切片?

网络切片是电信领域的一个突破性概念&#xff0c;它允许将物理网络基础设施划分为多个虚拟网络&#xff0c;称为切片。每个切片作为一个独立的网络运行&#xff0c;拥有自己的专用资源和定制的特性&#xff0c;满足不同应用、行业或用户的特定需求。 将网络切片视为在共享物理…

计算机网络——交换机和路由器

目录 前言 引言 交换机是用来做什么的&#xff1f; 与路由器有什么区别&#xff1f; 网关 子网掩码 网关、路由 前言 本博客是博主用于复习计算机网络的博客&#xff0c;如果疏忽出现错误&#xff0c;还望各位指正。 这篇博客是在B站掌芝士zzs这个UP主的视频的总结&am…

【论文阅读】Digging Into Self-Supervised Monocular Depth Estimation

论文&#xff1a;https://arxiv.org/pdf/1806.01260.pdf 代码&#xff1a;https://github.com/nianticlabs/monodepth2 Q: 这篇论文试图解决什么问题&#xff1f; A: 这篇论文试图解决的问题是如何提高仅使用单目图像进行深度估计的性能。具体来说&#xff0c;它关注的是如何…

[大模型]Qwen1.5-7B-Chat-GPTQ-Int4 部署环境

Qwen1.5-7B-Chat-GPTQ-Int4 部署环境 说明 Qwen1.5-72b 版本有BF16、INT8、INT4三个版本&#xff0c;三个版本性能接近。由于BF16版本需要144GB的显存&#xff0c;让普通用户忘却止步&#xff0c;而INT4版本只需要48GB即可推理&#xff0c;给普通用户本地化部署创造了机会。&…

Linux 函数学习 poll

1、Linux poll 函数 int poll(struct pollfd *fds, nfds_t nfds, int timeout); fds&#xff1a; 需要轮询的fd集合 nfds&#xff1a;需要轮询的fds数量 timeout&#xff1a;超时时间 返回值&#xff1a;0 超时&#xff0c;<0 发生异常&#xff0c;> 0 存在数据变化 …

函数、指针和数组的相互运用(C语言)

1、函数指针数组 含义&#xff1a;数组的每个元素都是函数指针类型.eg&#xff1a; &#xff08;此代码链接&#xff1a;http://t.csdnimg.cn/ClJmb.也可以在我发布博客中找到&#xff09; 2、指向函数指针数组的指针 1、引入 3、回调函数 1、含义&#xff1a;就是一个通过…