设计模式之组合模式-创建层次化的对象结构

目录

  • 概述
    • 概念
    • 主要角色
    • 应用场景
  • 组合模式的实现
    • 类图
    • NS图
    • 基本代码
    • 组合模式的精髓
    • 意外收获(❀❀)
  • 应用示例-公司组织架构管理
    • 需求
    • 结构图
    • 代码
  • 组合模式的优缺点
    • 优点
    • 缺点
  • 总结

概述

概念

    组合模式是一种结构型设计模式,它允许将对象组合成树形结构来表示“部分-整体”的层次关系。组合模式允许客户端统一处理单个对象和组合对象,使得客户端可以将它们视为相同的数据类型。
    在组合模式中,有两种主要类型的对象:叶子对象和容器对象。叶子对象是组合的最小单位,它不包含任何子对象;而容器对象包含叶子对象和/或其他容器对象,从而形成了一个递归的树形结构。

主要角色

  • Component:组合中的对象声明接口,它定义了组合对象的基本行为,包括添加和删除组成部分等操作。

  • Leaf:叶子是组合模式中的基本角色,它表示组合对象中的单个对象,通常是不可变的。叶子通常没有子节点,它们只包含一些基本的属性和行为。

  • Composite:是组合模式中的核心角色,它由多个组件构成,可以包含叶子节点和非叶子节点。组合对象是可变的,它们可以通过添加或删除组成部分来改变自己的状态。组合对象负责管理自己的组成部分,并提供一些特定于组合对象的操作,如添加或删除组成部分等。

    叶子节点和非叶子节点都是组件的一种,它们都实现了 Component 接口。

    在使用组合模式时,客户端需要知道如何处理组合对象和单个对象,以及如何使用抽象组件中定义的接口来访问它们的行为。具体组件和抽象组件则负责实现组合对象和单个对象的具体行为。组合对象则负责管理它包含的组成部分,并提供一些特定于组合对象的操作。

应用场景

  • 文件系统中的文件和文件夹
  • 菜单中的菜单项和菜单
  • 网页中的页面和页面元素

组合模式的实现

    组合模式的实现需要定义一个抽象基类和两个具体的实现类。抽象基类中定义了组合对象和单个对象的共同接口,具体实现类中分别实现了组合对象和单个对象的具体行为。

类图

在这里插入图片描述

NS图

在这里插入图片描述

基本代码

抽象类

abstract class Component {protected String name;public Component(String name){this.name=name;}public abstract void add(Component component);public abstract void remove(Component component);public abstract void display(int depth);
}

组合对象


public class Composite extends Component {public ArrayList<Component> children = new ArrayList<Component>();public Composite(String name) {super(name);}@Overridepublic void add(Component component) {children.add(component);}@Overridepublic void remove(Component component) {children.remove(component);}@Overridepublic void display(int depth) {//显示枝结点名称for (var i = 0; i < depth; i++) {System.out.print("-");}System.out.println(name);//对其下级进行遍历for (Component item : children) {item.display(depth + 2);}}
}

叶子节点


public class Leaf extends Component{public Leaf(String name){super(name);}//为了消除叶子结点和枝结点的区别,所以他们具备相同的接口,即便叶子结点不需要这两个功能@Overridepublic void add(Component component) {System.out.println("不能添加叶子了");}@Overridepublic void remove(Component component) {System.out.println("不能移除叶子");}@Overridepublic void display(int depth) {
//叶子结点的显示方法,只显示其名称和级别for(var i=0;i<depth;i++){System.out.print("-");}System.out.println(name);}
}

客户端


public class Client {public static void main(String[] args) {Composite root=new Composite("root");root.add(new Leaf("Leaf A"));root.add(new Leaf("Leaf B"));Component comp=new Composite("composite X");comp.add(new Leaf("Leaf XA"));comp.add(new Leaf("Leaf XB"));root.add(comp);Component comp2=new Composite("composite XY");comp2.add(new Leaf("Leaf XYA"));comp2.add(new Leaf("Leaf XYB"));comp.add(comp2);Leaf leaf=new Leaf("leaf C");root.add((leaf));Leaf leaf2=new Leaf("leaf D");root.add((leaf2));root.remove(leaf2);root.display(1);}
}

输出结果
在这里插入图片描述

组合模式的精髓

    在于将单个对象和组合对象统一对待,从而使得客户端可以一视同仁地操作它们,而无需关心其具体类型。这种设计思想体现了面向对象设计中的几个重要原则和模式:

  1. 透明性:组合模式通过将组合对象和叶子对象抽象成统一的组件类,让客户端对它们进行透明处理。客户端不需要知道正在处理的是叶子对象还是组合对象,只需通过统一的接口与它们交互。
  2. 单一职责原则:组合模式将对象的结构和行为分离开来,每个对象专注于自己的职责。叶子对象负责完成具体的操作,而组合对象负责管理子对象。
  3. 易扩展性:当系统需要增加新的叶子对象或组合对象时,组合模式无需修改现有的客户端代码,而是通过扩展组件类来实现。这提高了系统的灵活性和可扩展性。
  4. 清晰的层次结构:组合模式能够清晰地描述对象的层次结构,使得系统的整体结构更加直观和易于理解。

在代码中的体现:

    客户端在创建对象时,无论是Leaf还是Composite,使用相同的构造函数或方法来创建对象,比如new Leaf(“Leaf A”)和new Composite(“composite X”)。

    在添加子对象时,客户端使用相同的方法add来添加子对象,无论是添加Leaf还是添加Composite对象。

    在移除对象时,客户端同样使用相同的方法remove来移除对象,无论是移除Leaf还是移除Composite对象。

    这些操作都体现了客户端代码以统一的方式处理单个对象和对象的组合,而无需关心它们具体是哪一种类型。这种设计使得客户端代码更加简洁、清晰,并且更容易适应系统变化。

    这样的原因除了因为Composite和leaf都继承于Component。也可以从树的角度进行思考,比如下图,当把组合对象以及下面的叶对象看出一个整体,对于根组合对象来说,也可以当成一个叶对象。
在这里插入图片描述

意外收获(❀❀)

    调试代码中,还看到了一个特别有意思的现象:
    创建Composite对象的时候,先走父类构造方法,此时children:null
在这里插入图片描述
    走完父类构造之后,先走了public ArrayList children = new ArrayList();之后children:size=0,再这之后才结束构造方法
在这里插入图片描述
    children的变化说明了什么呢?为什么会这样呢?

    因为在Java中,当一个对象被创建时,成员变量会先被初始化为默认值,对于引用类型来说,默认值是null。这也解释了为什么在调试时会看到children开始是null。

children的变化如下:

    首先,children作为Composite的成员变量被初始化为null。
    然后,会调用Composite父类的构造函数super(name)来初始化父类的属性和状态。
    最后,执行Composite类的构造函数体中的其他代码,在这里手动将children赋值为new ArrayList()来创建一个新的ArrayList对象并赋给children。

    因此,在调试时会看到children开始是null,然后在执行完父类构造函数之后,children才不是null。

最后想说:

    在Java中,类的实例化过程分为以下几个步骤:

1、所有的成员变量(包括实例变量和类变量)都会先被初始化为默认值。对于引用类型来说,默认值是null,对于数值类型来说,默认值是0或0.0,布尔类型的默认值是false。

2、然后会调用相应的构造函数进行初始化,其中可以在构造函数中对成员变量进行进一步赋值或初始化操作

    在调试时看到children开始是null,正是因为它在实例化时被初始化为默认值null。而在执行完父类构造函数后,手动将children赋值为new ArrayList(),从而创建了一个新的ArrayList对象并赋给children。

    再加一条,作为一个带着叶子节点的Composite,是组合好一家子之后才往父节点挂靠的,也就和前面我说的作为一个整体可以看成一个叶子。
具体代码如下:
在这里插入图片描述
示意图:
在这里插入图片描述
    虽然代码中取名children,不要理解成子节点,举个例子,如果Composite是妈妈,children不是孩子,而是子宫,子宫用来添加孩子(叶子),说这个的原因,是因为我被开始的自己的理解蠢哭了
在这里插入图片描述
在这里插入图片描述

应用示例-公司组织架构管理

需求

    使用组合模式管理公司组织架构可以帮助公司更好地组织和管理其内部的部门、员工以及其它相关的业务实体。业务需求层级结构管理:公司内部通常存在多层级的组织结构,包括总公司、分公司、部门、小组等。需要一个灵活的方式来管理这种层级关系,使得可以方便地进行增加、删除、修改各个层级的组织单元。

结构图

在这里插入图片描述

代码

抽象公司类

abstract class Company {protected String name;public Company(String name ){this.name=name;}public abstract void  add(Company company);public abstract void remove(Company company);public abstract void display(int dept);public abstract void lineOfDuty();//履行职责,不同部门需履行不同的职责
}

组织类


public class ConcreteCompany extends Company {protected ArrayList<Company> children = new ArrayList<Company>();public ConcreteCompany(String name) {super(name);}@Overridepublic void add(Company company) {children.add(company);}@Overridepublic void remove(Company company) {children.remove(company);}@Overridepublic void display(int dept) {for (var i = 0; i < dept; i++)System.out.print("-");System.out.println(name);for (Company item : children) {item.display(dept + 2);//为什么+2}}@Overridepublic void lineOfDuty() {for (Company item : children) {item.lineOfDuty();}}
}

财务部门


public class FinanceDepartment extends Company{public FinanceDepartment(String name){super(name);}@Overridepublic void add(Company company) {}@Overridepublic void remove(Company company) {}@Overridepublic void display(int dept) {for (var i = 0; i <dept ; i++) {System.out.print("-");}System.out.println(name);}@Overridepublic void lineOfDuty() {System.out.println(name+"公司财务管理");}
}

人事部门


public class HRDepartment extends Company{public HRDepartment(String name){super(name);}@Overridepublic void add(Company company) {}@Overridepublic void remove(Company company) {}@Overridepublic void display(int dept) {for (var i = 0; i <dept ; i++) {System.out.print("-");}System.out.println(name);}@Overridepublic void lineOfDuty() {System.out.println(name+"员工招聘培训管理");}
}

客户端

public class Client {public static void main(String[] args) {Company root=new ConcreteCompany("北京总公司");root.add(new HRDepartment("总公司人力资源部"));root.add(new FinanceDepartment("总公司财务部"));Company comp=new ConcreteCompany("西南分公司");comp.add(new HRDepartment("西南分公司人力资源部"));comp.add(new FinanceDepartment("西南分公司财务部") );root.add(comp);Company comp2=new ConcreteCompany("成都分公司");comp2.add(new HRDepartment("成都分公司人力资源部"));comp2.add(new FinanceDepartment("成都分公司财务部") );comp.add(comp2);Company comp3=new ConcreteCompany("重庆分公司");comp3.add(new HRDepartment("重庆分公司人力资源部"));comp3.add(new FinanceDepartment("重庆分公司财务部") );comp.add(comp3);System.out.println("结构图:");root.display(1);System.out.println("职责");root.lineOfDuty();}
}

输出结果
在这里插入图片描述

组合模式的优缺点

优点

  • 可以将复杂的层次结构变得简单化
  • 可以统一处理组合对象和单个对象
  • 可以提高代码的复用性和可维护性

缺点

  • 对于大型的层次结构,组合模式可能会导致性能问题
  • 组合模式的实现需要定义多个类,增加了代码量
  • 组合模式的理解和实现需要一定的设计模式知识

总结

    组合模式是一种常用的设计模式,它可以将复杂的层次结构变得简单化,并且可以统一处理组合对象和单个对象。通过组合模式,可以将客户端代码与对象的层次结构分离,客户端只需要面向抽象构件(Component)进行操作,无需关心具体是哪种类型的对象。
    总的来说,组合模式的核心思想是将对象组织成树形结构,并提供统一的接口,使得客户端可以一致地处理单个对象和对象的组合。这种模式在处理具有层次结构的对象时非常有用,如组织架构、文件系统等。

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

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

相关文章

【Linux】 reboot 命令使用

reboot 命令用于用来重新启动计算机。 语法 reboot [参数] 命令选项及作用 执行令 man --reboot 执行命令结果 参数 -n : 在重开机前不做将记忆体资料写回硬盘的动作-w : 并不会真的重开机&#xff0c;只是把记录写到 /var/log/wtmp 档案里-d : 不把记录写到 /var/log…

拓世科技集团打造数字人营销解决方案,为车企提供新的“增长担当”

汽车&#xff0c;已经渐渐融入了现代人的日常生活&#xff0c;从高端的身份标志转变为普罗大众的出行选择&#xff0c;它驶入了千家万户&#xff0c;成为了我们日常出行的可靠伙伴&#xff0c;见证着人们的生活故事和时代的变迁。 中国汽车市场的蓬勃发展引起了业内外的广泛关…

解决Jenkins执行git脚本时报错:No such device or address问题

问题现象&#xff1a; Jenkins执行BeanShell脚本时&#xff0c;报错&#xff1a;jenkins fatal: could not read Username for http://112.11.120.1: No such device or address 解决方案&#xff1a; 解决服务器拉取git仓库的代码权限&#xff0c;使用高级子模块克隆功能。…

深入解析 Redis 分布式锁原理

一、实现原理 1.1 基本原理 JDK 原生的锁可以让不同线程之间以互斥的方式来访问共享资源&#xff0c;但如果想要在不同进程之间以互斥的方式来访问共享资源&#xff0c;JDK 原生的锁就无能为力了。此时可以使用 Redis 来实现分布式锁。 Redis 实现分布式锁的核心命令如下&am…

投票助手图文音视频礼物打赏流量主小程序开源版开发

投票助手图文音视频礼物打赏流量主小程序开源版开发 图文投票&#xff1a;用户可以发布图文投票&#xff0c;选择相应的选项进行投票。 音视频投票&#xff1a;用户可以发布音视频投票&#xff0c;观看音视频后选择相应的选项进行投票。 礼物打赏&#xff1a;用户可以在投票过…

cpu 支持内存带宽与内存最大长度的关系《鸟哥的 Linux 私房菜》

鸟哥的 Linux 私房菜 -- 计算机概论 -- 計算机&#xff1a;辅助人脑的好工具 同理&#xff0c;64 位 cpu 一次接受内存传递的 64bit 数据&#xff0c;内存字节地址用 64 位记录&#xff0c;最多能记录2^64个字节2^64Bytes2^34GB17179869184GB2^24TB&#xff0c;理论上&#xff…

Java后端开发——JDBC入门实验

JDBC&#xff08;Java Database Connectivity&#xff09;是Java编程语言中用于与数据库建立连接并进行数据库操作的API&#xff08;应用程序编程接口&#xff09;。JDBC允许开发人员连接到数据库&#xff0c;执行各种操作&#xff08;如插入、更新、删除和查询数据&#xff09…

计算机考研408有多难?25考研经验贴,开个好头很有必要

前言 大家好&#xff0c;我是陈橘又青&#xff0c;相信关注我的各位小伙伴们中&#xff0c;大多都是在计算机专业的大学生吧&#xff01; 每天都有许多人在后台私信我&#xff0c;问我要不要考研&#xff0c;我想说这个东西是因人而异的&#xff0c;像我本人就选择了就业&…

基于公共业务提取的架构演进——外部依赖防腐篇

1.背景 有了前两篇的帐号权限提取和功能设置提取的架构演进后&#xff0c;有一个问题就紧接着诞生了&#xff0c;对于诸多业务方来说&#xff0c;关键数据源的迁移如何在各个产品落地&#xff1f; 要知道这些数据都很关键&#xff1a; 对于帐号&#xff0c;获取不到帐号信息是…

如何像专家一样高效使用搜索引擎?适用于百度Baidu、谷歌Google

你几乎可以在互联网上搜索到任何内容,而Google是大多数人选择搜索信息的主要途径之一。 尽管频繁地使用Google,但是大部分互联网用户都不知道如何快速和高效地使用Google搜索。 可以说使用Google是一门艺术。 想要获得正确的答案,你需要提出正确的问题。想要快速地获得正…

【ElasticSearch系列-07】ES的开发场景和索引分片的设置及优化

ElasticSearch系列整体栏目 内容链接地址【一】ElasticSearch下载和安装https://zhenghuisheng.blog.csdn.net/article/details/129260827【二】ElasticSearch概念和基本操作https://blog.csdn.net/zhenghuishengq/article/details/134121631【三】ElasticSearch的高级查询Quer…

简单好看个人引导页毛玻璃页面 HTML 源码

毛玻璃个人引导页源码&#xff0c;界面简洁&#xff0c;已测可完美搭建&#xff0c;UI非常不错的&#xff0c;有兴趣的自行去安装体验吧&#xff0c;其它就没什么好介绍的了。 学习资料源代码&#xff1a;百度网盘 请输入提取码&#xff1a;ig8c

读程序员的制胜技笔记08_死磕优化(上)

1. 过早的优化是万恶之源 1.1. 著名的计算机科学家高德纳(Donald Knuth)的一句名言 1.2. 原话是&#xff1a;“对于约97%的微小优化点&#xff0c;我们应该忽略它们&#xff1a;过早的优化是万恶之源。而对于剩下的关键的3%&#xff0c;我们则不能放弃优化的机会。” 2. 过早…

适合汽车音频系统的ADAU1977WBCPZ、ADAU1978WBCPZ、ADAU1979WBCPZ四通道 ADC,24-bit,音频

一、ADAU1977WBCPZ 集成诊断功能的四通道ADC&#xff0c;音频 24 b 192k IC&#xff0c;SPI 40LFCSP ADAU1977集成4个高性能模数转换器(ADC)&#xff0c;其直接耦合输入具有10 V rms性能。该ADC采用多位Σ-Δ架构&#xff0c;其连续时间前端能够实现低EMI性能。它可以直接连接…

11.9存储器实验总结(单ram,双ram,FIFO)

实验设计 单端口RAM实现 双端口RAM实现 FIFO实现 文件结构为

python-jupyter实现OpenAi语音对话聊天

1.安装jupyter 这里使用的是jupyter工具&#xff0c;安装时需要再cmd执行如下命令&#xff0c;由于直接执行pip install jupyter会很慢&#xff0c;咱们直接使用国内源 pip install --user jupyter -i http://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.t…

Python按类别和比例从Labelme数据集中划分出训练数据集和测试数据集

Python按类别和比例从Labelme数据集中划分出训练数据集和测试数据集 前言前提条件相关介绍实验环境按类别和比例从Labelme数据集中划分出训练数据集和测试数据集代码实现输出结果 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容&#x…

真正解决jellyfin硬解码转码

前段时间入手一个DS423集成显卡UHD600&#xff0c;搭了一个jellyfin&#xff0c;发现网上关于硬解码的教程基本都存在问题&#xff0c;没有真正解决我的硬解码问题。经过一系列分析修改&#xff0c;最终实现硬解码。先贴效果图&#xff1a; 下载安装jellyfin这里就不叙述&#…

Maven-构建生命周期与插件

一、概念和基础 Maven针对项目的构建和发布定义了一系列明确的步骤&#xff0c;根据作用不同这些步骤分属于不同的生命周期。Maven针对每个步骤都有对应的默认插件&#xff0c;Maven在构建过程中是通过调用这些插件完成整个过程的。开发者只需要通过简单的命令就可以驱动maven…

若依分离版——使用Knife4j 自动生成接口文档

背景&#xff1a; 前后端分离程序&#xff0c;如果需要前端开发人员和后端开发人员配合开发&#xff0c;则需要将接口文档并显性给前端人员 解决办法&#xff1a; 使用knife4j替代若依自带的swagger&#xff0c;因为knife4j是在swagger基础上包装的&#xff0c;Knife4j不仅具…