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

目录

  • 概述
    • 概念
    • 主要角色
    • 应用场景
  • 组合模式的实现
    • 类图
    • 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);

    }

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

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

    @Override
    public 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);
    }
    //为了消除叶子结点和枝结点的区别,所以他们具备相同的接口,即便叶子结点不需要这两个功能
    @Override
    public void add(Component component) {
        System.out.println("不能添加叶子了");

    }

    @Override
    public void remove(Component component) {
        System.out.println("不能移除叶子");
    }

    @Override
    public 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);
    }

    @Override
    public void add(Company company) {
        children.add(company);
    }

    @Override
    public void remove(Company company) {
        children.remove(company);
    }

    @Override
    public 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
        }
    }

    @Override
    public void lineOfDuty() {
        for (Company item : children) {
            item.lineOfDuty();
        }
    }
}

财务部门


public class FinanceDepartment extends Company{
    public FinanceDepartment(String name){
        super(name);
    }
    @Override
    public void add(Company company) {

    }

    @Override
    public void remove(Company company) {

    }

    @Override
    public void display(int dept) {
        for (var i = 0; i <dept ; i++) {
            System.out.print("-");
        }
        System.out.println(name);
    }

    @Override
    public void lineOfDuty() {
        System.out.println(name+"公司财务管理");
    }
}

人事部门


public class HRDepartment extends Company{
    public HRDepartment(String name){
        super(name);
    }
    @Override
    public void add(Company company) {

    }

    @Override
    public void remove(Company company) {

    }

    @Override
    public void display(int dept) {
        for (var i = 0; i <dept ; i++) {
            System.out.print("-");
        }
        System.out.println(name);
    }

    @Override
    public 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)进行操作,无需关心具体是哪种类型的对象。
    总的来说,组合模式的核心思想是将对象组织成树形结构,并提供统一的接口,使得客户端可以一致地处理单个对象和对象的组合。这种模式在处理具有层次结构的对象时非常有用,如组织架构、文件系统等。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

虚幻5.1 常见的效果关闭方式

常见的虚幻效果关闭方式 1.Bloom ProjectSettings->Rendering->Default Settings->Bloom PostProcessVolume->Lens->Bloom 2.Ambient Occlusion/Screen Space Ambient Occlusion(SSAO) ProjectSettings->Rendering->Default Settings->Ambient Occl…

服务器部署 Nacos 获取不到配置浏览器可以访问

服务器部署 Nacos 获取不到配置浏览器可以访问 &#x1f4d4; 千寻简笔记介绍 千寻简笔记已开源&#xff0c;Gitee与GitHub搜索chihiro-notes&#xff0c;包含笔记源文件.md&#xff0c;以及PDF版本方便阅读&#xff0c;且是用了精美主题&#xff0c;阅读体验更佳&#xff0c…

基于springboot vue mysql的在线拍卖系统 全套代码 全套文档

基于SpringBoot的在线拍卖系统,springboot vue mysql (毕业论文10168字以上,共34页,程序代码,MySQL数据库) 代码下载链接&#xff1a;https://pan.baidu.com/s/104LjKF7kvhYeooSBk9h65g?pwd8fk4 提取码&#xff1a;8fk4 【运行环境】 IDEA, JDK1.8, Mysql, Node, Vue 【技…

【每日一题】318. 最大单词长度乘积-2023.11.6

题目&#xff1a; 318. 最大单词长度乘积 给你一个字符串数组 words &#xff0c;找出并返回 length(words[i]) * length(words[j]) 的最大值&#xff0c;并且这两个单词不含有公共字母。如果不存在这样的两个单词&#xff0c;返回 0 。 示例 1&#xff1a; 输入&#xff1…

模拟ASP.NET Core MVC设计与实现

前几天有人在我的《ASP.NET Core框架揭秘》读者群跟我留言说&#xff1a;“我最近在看ASP.NET Core MVC的源代码&#xff0c;发现整个系统太复杂&#xff0c;涉及的东西太多&#xff0c;完全找不到方向&#xff0c;你能不能按照《200行代码&#xff0c;7个对象——让你了解ASP.…

第26期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…

【Git】快速入门安装及使用git与svn的区别常用命令

一、导言 1、什么是svn&#xff1f; SVN是Subversion的简称&#xff0c;是一个集中式版本控制系统。与Git不同&#xff0c;SVN没有分布式的特性。在SVN中&#xff0c;项目的代码仓库位于服务器上&#xff0c;团队成员通过向服务器提交和获取代码来实现版本控制。SVN记录了每个文…

Jmeter+ant+jenkins接口自动化测试

平台简介 一个完整的接口自动化测试平台需要支持接口的自动执行&#xff0c;自动生成测试报告&#xff0c;以及持续集成。Jmeter 支持接口的测试&#xff0c;Ant 支持自动构建&#xff0c;而 Jenkins 支持持续集成&#xff0c;所以三者组合在一起可以构成一个功能完善的接口自动…

聊聊室内导航在应用方面

大家去大型的商场时&#xff0c;应该都见过一些提示牌&#xff0c;微信扫一扫导航。当拿微信扫了之后&#xff0c;就会打开一个小程序&#xff0c;里面显示整个商场的二维或三维的平面结构&#xff0c;以及当前自己的位置。此时可以通过手机快速的查看商场内其他的商铺、公共区…

SQL Server SSIS的安装

标题SQL SERVER 安装 下载SQL SERVER数据库&#xff1a;&#xff08;以SQL SERVER 2022 Developer版本&#xff09;(https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads?rtc1) 以administrator权限安装&#xff1a; 下载完成后&#xff0c;会出现以下界面&a…

R语言 PPT 预习+复习

什么狗吧发明的结业考&#xff0c;站出来和我对线 第一章 绪论 吊码没有&#xff0c;就算考R语言特点我也不背&#xff0c;问就是叫么这没用。 第二章 R语言入门 x<-1:20 赋值语句 x 1到20在x上添加均值为0、标准差为2的正态分布噪声 y <- x rnorm (20, 0, 2) 这…

【leaflet】1. 初见

▒ 目录 ▒ &#x1f6eb; 导读需求开发环境 1️⃣ 概念概念解释特点 2️⃣ 学习路线图3️⃣ html示例&#x1f6ec; 文章小结&#x1f4d6; 参考资料 &#x1f6eb; 导读 需求 要做游戏地图了&#xff0c;看到大量产品都使用的leaflet&#xff0c;所以开始学习这个。 开发环境…

微服务中配置文件(YAML文件)和项目依赖(POM文件)的区别与联系

实际上涉及到了微服务架构中的两个重要概念&#xff1a;服务间通信和项目依赖管理。在微服务架构中&#xff0c;一个项目可以通过两种方式与另一个项目建立依赖关系&#xff1a;通过配置文件&#xff08;如YAML文件&#xff09;和通过项目依赖&#xff08;如POM文件&#xff09…

【每日一题】逃离火灾

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;二分枚举空间复杂度&#xff1a; O ( m n ) O(mn) O(mn)。 写在最后 Tag 【二分答案】【BFS】【数组】【2023-11-09】 题目来源 2258. 逃离火灾 题目解读 现在有一个人在一个二维网格的左上角&#xff0c;坐标 (0, 0…

SpringCache(Redis)

一、springcache是什么 springcache是spring的缓存框架&#xff0c;利用了AOP&#xff0c;实现了基于注解的缓存功能&#xff0c;并且进行了合理的抽象&#xff0c;业务代码不用关心底层是使用了什么缓存框架&#xff0c;只需要简单地加一个注解&#xff0c;就能实现缓存功能了…

pytorch中常用的损失函数

1 损失函数的作用 损失函数是模型训练的基础&#xff0c;并且在大多数机器学习项目中&#xff0c;如果没有损失函数&#xff0c;就无法驱动模型做出正确的预测。 通俗地说&#xff0c;损失函数是一种数学函数或表达式&#xff0c;用于衡量模型在某些数据集上的表现。损失函数在…

计算机网络期末复习-Part2

1、网络应用程序体系结构 &#xff08;1&#xff09;客户端/服务器&#xff08;C/S&#xff09;体系结构&#xff1a; 客户端/服务器&#xff08;C/S&#xff09;应用程序&#xff1a; Web浏览器与Web服务器&#xff1a;当您使用Web浏览器&#xff08;客户端&#xff09;访问…

Scala爬虫如何实时采集天气数据?

这是一个基本的Scala爬虫程序&#xff0c;使用了Scala的http library来发送HTTP请求和获取网页内容。在爬取天气预报信息时&#xff0c;我们首先需要创建一个代理对象proxy&#xff0c;并将其用于发送HTTP请求。然后&#xff0c;我们使用http库的GET方法获取网页内容&#xff0…

linux修改rocketmq的日志文件位置

文章目录 &#x1f50a;修改rocketmq的日志文件位置&#x1f4d5;原来的文件&#x1f4cc;修改后文件&#x1f4c7;rocketmq中的Rocketmq_client.log文件在配置文件中改不了 需要在代码logback文件中进行修改&#x1f58a;️最后总结 &#x1f50a;修改rocketmq的日志文件位置 …

【Linux】 awk命令使用

AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。 之所以叫 AWK 是因为其取了三位创始人 Alfred Aho&#xff0c;Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符。 语法 awk [选项] [文件] awk [选项] [程序] [文件] awk命令 -Linux手…