结构型设计模式05-组合模式

news2024/12/28 20:46:15

🧑‍💻作者:猫十二懿

❤️‍🔥账号:CSDN 、掘金 、个人博客 、Github

🎉公众号:猫十二懿

组合模式

1、组合模式介绍

组合模式(Composite Pattern),又叫部分整体模式,是一种结构型设计模式。它可以将一组相似的对象看作一个单一的对象,并在树形结构中组合这些对象,用来表示部分以及整体层次关系。这种模式创建了一个包含自己对象组的类。 其中,叶子节点表示组合中的单个对象,而容器节点则表示包含其他节点的复合对象。容器节点和叶子节点可以被看作是同一种类型的对象,因此可以对它们进行同样的操作。这种模式常被用于处理树形数据结构和递归结构的问题。

1.1 组合模式基本实现

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

组合模式结构图:

image-20230510171425515

Component为组合中的对象声明接口,在适当情况下,实现所有类共有接 口的默认行为。声明一个接口用于访问和管理Component的子部件。

/**
 * @author Shier
 * CreateTime 2023/5/10 17:16
 */
public abstract class Component {
    protected String name;

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

    /**
     * 增加节点
     *
     * @param component
     */
    public abstract void add(Component component);

    /**
     * 移除
     *
     * @param component
     */
    public abstract void remove(Component component);

    /**
     * 树结构
     */
    public abstract void display(int depth);
}

Leaf在组合中表示叶节点对象,叶节点没有子节点。

/**
 * @author Shier
 * CreateTime 2023/5/10 17:20
 */
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 (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
    }
}

Composite定义有枝节点行为,用来存储子部件,在Component接口中实 现与子部件有关的操作,比如增加add和删除remove。

/**
 * @author Shier
 * CreateTime 2023/5/10 17:23
 */
public class Composite extends Component {

    /**
     * 子对象集合依赖存储下属的枝节点和叶子节点
     */
    private List<Component> childrenList = new ArrayList<Component>();


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

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

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

    @Override
    public void display(int depth) {
        // 叶节点具体显示方法,显示名称和级别
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
        // 遍历下级节点
        for (Component component : childrenList) {
            component.display(depth + 1);
        }
    }
}

客户端代码,能通过Component接口操作组合部件的对象。

/**
 * @author Shier
 * CreateTime 2023/5/10 17:27
 */
public class CombinationClient {
    public static void main(String[] args) {
        // 根节点
        Composite root = new Composite("root");
        root.add(new Leaf("Leaf A"));
        root.add(new Leaf("Leaf B"));

        // composite1 为root的子节点,其本身下还具有两个叶子节点
        Composite composite1 = new Composite("Composite 1");
        composite1.add(new Leaf("Leaf 1-A"));
        composite1.add(new Leaf("Leaf 1-B"));
        root.add(composite1);

        // composite1-1 为 composite1 的子节点,其本身下还具有两个叶子节点
        Composite composite2 = new Composite("Composite 1-1");
        composite2.add(new Leaf("Leaf 1-1-A"));
        composite2.add(new Leaf("Leaf 1-1-B"));
        composite1.add(composite2);

        // 根部还有两个叶子节点 C 和 D
        Leaf leaf2 = new Leaf("Leaf C");
        root.add(leaf2);
        // 3 被移除了
        Leaf leaf3 = new Leaf("Leaf D");
        root.add(leaf3);
        root.remove(leaf3);

        root.display(1);
    }
}

输出结果:

image-20230510173905130

Leaf类当中也有add和remove,树叶 不是不可以再长分枝吗?

  1. 透明方式,也就是说,在Component中声明所有用 来管理子对象的方法,其中包括add、remove等。这样实现Component接口的 所有子类都具备了add和remove。这样做的好处就是叶节点和枝节点对于外 界没有区别,它们具备完全一致的行为接口。但问题也很明显,因为Leaf类 本身不具备add()、remove()方法的功能,所以实现它是没有意义的。那Leaf类当中不用add 和remove方法,可以吗?可以的,那就是使用安全方式。
  2. 安全方式,也就是在Component接口中不去声 明add和remove方法,那么子类的Leaf也就不需要去实现它,而是在 Composite中声明所有用来管理子类对象的方法,这样做就不会出现刚才提 到的问题,不过由于不够透明,所以树叶和树枝类将不具有相同的接口,客 户端的调用需要做相应的判断,带来了不便。

2、具体例子说明

例子:使用一个OA系统说明

大公司总部在北京,总部有人力资源部、财务部等,在上海有分部,其他地方还有办事处,南京办事处,杭州办事处

image-20230510184911248

OA系统:

OA系统(Office Automation System)是一种办公自动化系统,它利用计算机技术,将管理、协调和控制等工作自动化。它能够帮助各个部门和员工提高工作效率,降低沟通成本,简化办公流程,实现信息共享和协同工作。

OA系统的功能包括但不限于:电子邮件、日程安排、文档管理、工作流程、会议管理、车辆管理、资产管理等。这些功能能够帮助用户在办公过程中快速、准确、便捷地完成各种任务,提高工作效率。

OA系统还具有安全、稳定、可靠、易扩展等特点,能够适应企业、政府等各种单位的不同需求,并且随着信息化建设的发展,OA系统正在变得越来越智能化和人性化,更好地满足用户的需求。

2.1 公司管理系统

代码结构图

image-20230510185006071

公司类:抽象类或接口

/**
 * @author Shier
 * CreateTime 2023/5/10
 * 公司抽象类
 */
public abstract class Company {
    protected String name;

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

    /**
     * 增加部门
     *
     * @param component
     */
    public abstract void add(Company component);

    /**
     * 移除部门
     *
     * @param component
     */
    public abstract void remove(Company component);

    /**
     * 显示公司结构
     */
    public abstract void display(int depth);

    /**
     * 履行职责-不同的部门有不同的职责
     */
    public abstract void lineOfDuty();
}

具体公司类:实现接口,树枝节点。

/**
 * @author Shier
 * CreateTime 2023/5/10 17:23
 * 具体分公司
 */
public class ConcreteCompany extends Company {

    /**
     * 子对象集合依赖存储下属的枝节点和叶子节点
     */
    private List<Company> childrenList = new ArrayList<>();

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

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

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

    @Override
    public void display(int depth) {
        // 叶节点具体显示方法,显示名称和级别
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
        // 遍历下级节点
        for (Company component : childrenList) {
            component.display(depth + 1);
        }
    }

    /**
     * 履行职责
     */
    @Override
    public void lineOfDuty() {
        for (Company company : childrenList) {
            company.lineOfDuty();
        }
    }
}

人力资源部与财务部类:树叶节点。

/**
 * @author Shier
 * CreateTime 2023/5/10 17:20
 * 人力资源部,树叶节点
 */
public class HRDepartment extends Company {
    public HRDepartment(String name) {
        super(name);
    }

    /**
     * 这里是叶子节点,所以说增加和删除什么都不用干
     *
     * @param company
     */
    @Override
    public void add(Company company) {
    }

    @Override
    public void remove(Company company) {
    }

    @Override
    public void display(int depth) {
        // 叶节点具体显示方法,显示名称和级别
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
    }

    @Override
    public void lineOfDuty() {
        System.out.println(name + " 员工招聘培训管理");
    }
}
/**
 * @author Shier
 * CreateTime 2023/5/10 17:20
 * 财务部,树叶节点
 */
public class FinanceDepartment extends Company {
    public FinanceDepartment(String name) {
        super(name);
    }

    /**
     * 这里是叶子节点,所以说增加和删除什么都不用干
     *
     * @param company
     */
    @Override
    public void add(Company company) {
    }

    @Override
    public void remove(Company company) {
    }

    @Override
    public void display(int depth) {
        // 叶节点具体显示方法,显示名称和级别
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
    }

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

客户端:

/**
 * @author Shier
 * CreateTime 2023/5/10 17:27
 */
public class CompanyClient {
    public static void main(String[] args) {
        // 总部公司
        ConcreteCompany root = new ConcreteCompany("北京总公司");
        root.add(new HRDepartment("总公司人力资源部门"));
        root.add(new HRDepartment("总公司财务部门"));

        // 上海分公司 为总公司的子公司,其本身下还具有两个部门
        ConcreteCompany composite1 = new ConcreteCompany("上海华东分公司");
        composite1.add(new HRDepartment("华东分公司人力资源部"));
        composite1.add(new HRDepartment("华东分公司财务部"));
        root.add(composite1);

        // 南京办事处 为 华东分公司 的公司,其本身下还具有两个部门
        ConcreteCompany composite2 = new ConcreteCompany("南京办事处");
        composite2.add(new HRDepartment("南京办事处人力资源部门"));
        composite2.add(new HRDepartment("南京办事处财务部门"));
        composite1.add(composite2);

        // 根部还有其他部门
        HRDepartment leaf2 = new HRDepartment("其他部门");
        root.add(leaf2);

        System.out.println("公司管理结构图:");
        root.display(1);
        System.out.println("各自职责");
        root.lineOfDuty();
    }
}

输出结果:

image-20230510190508977

  1. 组合模式这样就定义了包含人力资源部和财务部这些基本对象和分公司、办事处等组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中, 任何用到基本对象的地方都可以使用组合对象了
  2. 用户是不用关心到底是处理一个叶节点还是处理一个组合组 件,也就用不着为定义组合而写一些选择判断语句了
  3. 组合模式让客户可以一致地使用组合结构和单个对 象

3、组合模式总结

组合模式优点:

  1. 简化客户端代码:组合模式使得客户端可以统一地处理单个对象和组合对象,不需要为处理它们而编写特殊的代码。客户端只需要调用相同的接口,无论是操作单个对象还是操作组合对象。
  2. 灵活性和可扩展性:由于组合模式使用了树形结构,可以很方便地添加、删除和修改对象,而不会对现有的代码产生太大的影响。这使得组合模式具有良好的灵活性和可扩展性。
  3. 提高代码复用性:组合模式通过将相同的操作应用于单个对象和组合对象,可以提高代码的复用性。不需要为每个对象编写重复的代码,而是可以在组合对象的层次结构中共享代码。

组合模式缺点:

  1. 可能会导致设计过度复杂:当对象的层次结构非常复杂时,使用组合模式可能会导致设计变得复杂。同时,过度使用组合模式也会增加系统的复杂性。
  2. 不容易限制组合对象的类型:组合模式通常会统一处理单个对象和组合对象,这意味着对组合对象的类型可能没有明确的限制。这可能导致某些操作在组合对象上没有意义或无法正确执行。

组合模式使用场景:

  1. 当需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时, 就应考虑用组合模式

  2. 当对象呈现出层次结构,并且您希望以统一的方式处理整体和部分时,可以考虑使用组合模式。例如,树形结构、文件系统等。

  3. 当您希望客户端能够忽略对象集合和单个对象之间的差异时,可以使用组合模式。客户端只需调用相同的方法,而无需关心操作的是单个对象还是组合对象。

  4. 当您希望在不更改现有代码的情况下添加新类型的对象时,可以考虑使用组合模式。组合模式对于扩展性和灵活性非常有利。

  5. 可能会导致设计过度复杂:当对象的层次结构非常复杂时,使用组合模式可能会导致设计变得复杂。同时,过度使用组合模式也会增加系统的复杂性。

  6. 不容易限制组合对象的类型:组合模式通常会统一处理单个对象和组合对象,这意味着对组合对象的类型可能没有明确的限制。这可能导致某些操作在组合对象上没有意义或无法正确执行。

组合模式使用场景:

  1. 当需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时, 就应考虑用组合模式
  2. 当对象呈现出层次结构,并且您希望以统一的方式处理整体和部分时,可以考虑使用组合模式。例如,树形结构、文件系统等。
  3. 当您希望客户端能够忽略对象集合和单个对象之间的差异时,可以使用组合模式。客户端只需调用相同的方法,而无需关心操作的是单个对象还是组合对象。
  4. 当您希望在不更改现有代码的情况下添加新类型的对象时,可以考虑使用组合模式。组合模式对于扩展性和灵活性非常有利。

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

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

相关文章

Generator-Evaluator重排模型在淘宝流式场景的实践

除了相关性&#xff0c;复杂信息流推荐场景还需要兼顾多样的业务需求&#xff0c;包括打散&#xff08;多样性&#xff09;&#xff0c;流量调控&#xff0c;多展示形态/多路供给融合等。传统推荐系统采用pipeline的形式&#xff0c;分步处理上述需求&#xff0c;缺少统筹优化&…

【博客650】irate适用于绘制细粒度灵敏图,但警惕用于告警

irate适用于绘制细粒度灵敏图&#xff0c;但警惕用于告警 1、irate解析 作用&#xff1a; irate(v range-vector) 函数用于计算区间向量的增长率&#xff0c;但是其反应出的是瞬时增长率。 原理&#xff1a; irate 函数是通过区间向量中最后两个两本数据来计算区间向量的增长…

C++表达式模板教程:从原理到应用的全面解析

C表达式模板教程 1. C表达式模板的引入 (Introduction to C Expression Templates)1.1 表达式模板的定义和作用 (Definition and Role of Expression Templates)1.2 表达式模板的历史和发展 (History and Development of Expression Templates)1.3 表达式模板在现代C中的地位 (…

java springboot VUE 在线学习平台系统开发mysql数据库web结构java编程计算机网页源码maven项目前后端分离

一、源码特点 springboot VUE 在线学习平台系统是一套完善的完整信息管理类型系统 前后端分离&#xff0c;结合springboot框架和VUE完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架 &#xff08;MVC模式开发&#xff09;&#xff0c;系统具有…

005Mybatis返回值(ResultMap 一对多,多对多)

属性 id 应该总是指定一个或多个可以唯一标识结果的属性。 虽然&#xff0c;即使不指定这个属性&#xff0c;MyBatis 仍然可以工作&#xff0c;但是会产生严重的性能问题。 只需要指定可以唯一标识结果的最少属性。显然&#xff0c;你可以选择主键&#xff08;复合主键也可以…

DevOps系列文章之 远程部署的一种方案

远程部署的一种方案 sshpass 一个简单、轻量级命令行工具&#xff0c;提供非交互式密码验证 原理 ssh 直接使用 TTY 访问&#xff0c;以确保密码是用户键盘输入的。 sshpass 在专门的 tty 中运行 ssh&#xff0c;以误导 ssh 相信它是从用户接收到的密码使用 sshpass 是不安…

深入理解HashMap源码

文章目录 HashMap简介源码分析关键参数获取数组下标put方法resize扩容过程jdk1.7的扩容实现jdk1.8的扩容实现 get()方法remove()方法 总结 关于HashMap&#xff0c;一直都是一个非常热门的话题&#xff0c;只要你出去面试&#xff0c;一定少不了它&#xff01; 本文主要结合 JD…

English Learning - L3 作业打卡 Lesson5 Day35 2023.6.8 周四

English Learning - L3 作业打卡 Lesson5 Day35 2023.6.8 周四 引言&#x1f349;句1: Publishers know that some people are self-conscious about what they read on public transport and so they put out different versions of a cover.成分划分弱读连读爆破语调 &#x…

实现表白墙

我们已经学习了Http以及Servlet类的相关知识 今天我们来实操一下,实现一个简单的既有前端又有后端的网站–表白墙 之前在学习前端的时候已经写过了表白墙的前端代码,存在两个问题 1.页面重启,数据丢失 2.数据只是在本地的,别人看不见 那么这样的问题我们要咋样解决呢? 引入…

黑马Redis视频教程高级篇(二:多级缓存)

目录 一、什么是多级缓存&#xff1f; 二、JVM进程缓存 2.1、导入案例 2.2、初识Caffeine 2.3、实现JVM进程缓存 2.3.1、需求 2.3.2、实现 三、Lua语法入门 3.1、初识Lua 3.2、HelloWord 3.3、变量和循环 3.3.1、Lua的数据类型 3.3.2、声明变量 3.3.3、循环 3.4…

Hadoop | 好用的脚本分享

知识目录 一、写在前面✨二、一键安装HA&#x1f36d;三、Hadoop一键启动&#x1f525;四、一键启动可视化工具&#x1f36d;五、结语&#x1f525; 一、写在前面✨ 大家好&#xff01;我是初心&#xff0c;希望我们一路走来能坚守初心&#xff01; &#x1f3e0; 个人主页&a…

2023版一线大厂Java面试八股文(最新版)1000+ 面试题附答案详解,最全面详细

Java 面试八股文有必要背吗&#xff1f; 我的回答是&#xff1a;很有必要。你可以讨厌这种模式&#xff0c;但你一定要去背&#xff0c;因为不背你就进不了大厂。现如今&#xff0c;Java 面试的本质就是八股文&#xff0c;把八股文面试题背好&#xff0c;面试才有可能表现好。…

Python爬虫之Scrapy框架系列(21)——重写媒体管道类实现保存图片名字自定义及多页爬取

目录&#xff1a; 重写框架自带媒体管道类部分方法实现保存图片名字的自定义&#xff1a;1.爬虫文件&#xff1a;2.items.py文件中设置特殊的字段名&#xff1a;3.settings.py文件中开启自建管道并设置文件存储路径&#xff1a;4.编写pipelines.py5.观察可发现完美实现&#xf…

JetBrains 激活方式的区别

文章目录 简介激活方式 简介 JetBrains 是一家全球知名的软件开发工具公司。 JetBrains 成立于 2000 年&#xff0c;总部位于捷克共和国的布拉格。该公司致力于为开发者提供高效、智能和创新的软件开发工具&#xff0c;以提升开发人员的生产力和开发体验。 JetBrains 的主要…

Redis的主从复制、哨兵机制、集群

一、主从复制 1、定义 主&#xff1a;master以写为主当master数据变化的时候从&#xff1a;slave以读为主自动将新的数据异步同步到其他slave数据库 2、作用 读写分离、容灾恢复、数据备份、水平扩容支撑高并发。 3、使用方式——配从不配主 权限配置&#xff1a;master如…

【图书推荐 | 13】后端系列

【赠书活动第十二期 】 图书推荐 本期书籍&#xff1a;后端系列 图书列表 本期图书列表&#xff1a; Spring Cloud 微服务快速上手项目驱动零起点学JavaNode.js 从基础到项目实战Diango Web 开发实例精解Flask Web 全栈开发实战精通Hadoopsmysql 数据库基础与实战应用Neo4j 图谱…

指针--用指针变量作函数参数的实例(按值调用与模拟按引用调用)、函数指针及其应用

一、用指针变量作函数参数的实例 思考题&#xff1a; 例题&#xff1a;从键盘输入某班学生某门课成绩&#xff08;每班人数最多不超过40人&#xff0c;具体人数由键盘输入&#xff09;&#xff0c;是分析下列程序是否能实现计算并输出最高分以及相应学号。 #include <stdi…

群晖 NAS 外网访问设置 - 腾讯 DNSPod

目录 ​编辑 一、使用DNSPod&#xff0c;实现DDNS&#xff08;动态域名&#xff09; 二、公共概念厘清 三、腾讯DNSPod上详细设置步骤 1. 打开DNSPod.cn网站并登录 2. 登录成功后&#xff0c;选择【我的域名】-> 【添加域名】 3. 添加群晖NAS需要二级域名&#xff08…

Visual Studio封装静态链接库至新静态库,供程序调用

熟悉Windows开发的人都肯定了解静态链接库和动态链接库。 最近遇到一个问题&#xff1a; A静态库是使用VS编译&#xff0c;因为C版本的问题&#xff0c;并不能直接在Qt中被调用&#xff0c;因为会报头文件某处错误。 因为A库很大&#xff0c;同时又不想修改太多A库源文件&#…

开源社章程(2023版)

第 1 条 开源社是由志愿贡献于开源事业的个人志愿者&#xff0c;依 “贡献、共识、共治” 原则所组成的开源社区。第 2 条 开源社的英文名称为“KAIYUANSHE”&#xff0c;官方网站地址为 https://kaiyuanshe.cn/第 3 条 开源社的愿景为&#xff1a;立足中国、贡献全球&#xff…