瑞_23种设计模式_组合模式

文章目录

    • 1 组合模式(Composite Pattern)
      • 1.1 介绍
      • 1.2 概述
      • 1.3 组合模式的结构
      • 1.4 组合模式的分类
      • 1.5 组合模式的优点
      • 1.6 组合模式的使用场景
    • 2 案例一
      • 2.1 需求
      • 2.2 代码实现
    • 3 案例二
      • 3.1 需求
      • 3.2 代码实现

🙊 前言:本文章为瑞_系列专栏之《23种设计模式》的组合模式篇。本文中的部分图和概念等资料,来源于博主学习设计模式的相关网站《菜鸟教程 | 设计模式》和《黑马程序员Java设计模式详解》,特此注明。本文中涉及到的软件设计模式的概念、背景、优点、分类、以及UML图的基本知识和设计模式的6大法则等知识,建议阅读 《瑞_23种设计模式_概述》

本系列 - 设计模式 - 链接:《瑞_23种设计模式_概述》

⬇️本系列 - 创建型模式 - 链接🔗

  单例模式:《瑞_23种设计模式_单例模式》
  工厂模式:《瑞_23种设计模式_工厂模式》
  原型模式:《瑞_23种设计模式_原型模式》
抽象工厂模式:《瑞_23种设计模式_抽象工厂模式》
 建造者模式:《瑞_23种设计模式_建造者模式》

⬇️本系列 - 结构型模式 - 链接🔗

  代理模式:《瑞_23种设计模式_代理模式》
 适配器模式:《瑞_23种设计模式_适配器模式》
 装饰者模式:《瑞_23种设计模式_装饰者模式》
  桥接模式:《瑞_23种设计模式_桥接模式》
  外观模式:《瑞_23种设计模式_外观模式》
  组合模式:《后续更新》
  享元模式:《后续更新》

⬇️本系列 - 行为型模式 - 链接🔗

模板方法模式:《后续更新》
  策略模式:《后续更新》
  命令模式:《后续更新》
 职责链模式:《后续更新》
  状态模式:《后续更新》
 观察者模式:《后续更新》
 中介者模式:《后续更新》
 迭代器模式:《后续更新》
 访问者模式:《后续更新》
 备忘录模式:《后续更新》
 解释器模式:《后续更新》

在这里插入图片描述

1 组合模式(Composite Pattern)

  组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

瑞:结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性

  这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

1.1 介绍

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

  • 主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

  • 何时使用
      1️⃣ 您想表示对象的部分-整体层次结构(树形结构)。
      2️⃣ 您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

  • 如何解决:树枝和叶子实现统一接口,树枝内部组合该接口。

  • 关键代码:树枝内部组合该接口,并且含有内部属性 List,里面放 Component。

  • 应用实例
      1️⃣ 算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。
      2️⃣ 在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。

  • 优点
      1️⃣ 高层模块调用简单。
      2️⃣ 节点自由增加。

  • 缺点:在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

  • 使用场景:部分、整体场景,如树形菜单,文件、文件夹的管理

  • 注意事项:定义时为具体类。

1.2 概述

  定义:又名部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

在这里插入图片描述
  对于这个图片肯定会非常熟悉,上图我们可以看做是一个文件系统,对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象即可是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象和叶子对象。

1.3 组合模式的结构

  • 组合模式主要包含三种角色:
      1️⃣ 抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
      2️⃣ 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
      3️⃣ 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

1.4 组合模式的分类

  在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式安全组合模式两种形式。

  1️⃣ 透明组合模式

  透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 addremovegetChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。

  透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)

  2️⃣ 安全组合模式

  在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

在这里插入图片描述

1.5 组合模式的优点

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
  • 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

1.6 组合模式的使用场景

  组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作。




2 案例一

【案例】软件菜单

2.1 需求

  如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。

在这里插入图片描述

  要实现该案例,我们先画出类图:

在这里插入图片描述

2.2 代码实现

菜单组件 (抽象类)
/**
 * 菜单组件 : 属于抽象根节点
 *
 * @author LiaoYuXing-Ray
 **/
public abstract class MenuComponent {
    // 菜单组件的名称
    protected String name;
    // 菜单组件的层级
    protected int level;

    // 添加子菜单
    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    // 移除子菜单
    public void remove(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    // 获取指定的子菜单
    public MenuComponent getChild(int index) {
        throw new UnsupportedOperationException();
    }

    // 获取菜单或者菜单项的名称
    public String getName() {
        return name;
    }

    // 打印菜单名称的方法(包含子菜单和字菜单项)
    public abstract void print();
}

这里的MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。

菜单类(类)
import java.util.ArrayList;
import java.util.List;

/**
 * 菜单类  : 属于树枝节点
 *
 * @author LiaoYuXing-Ray
 **/
public class Menu extends MenuComponent {

    // 菜单可以有多个子菜单或者子菜单项
    private final List<MenuComponent> menuComponentList = new ArrayList<MenuComponent>();

    // 构造方法
    public Menu(String name,int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void add(MenuComponent menuComponent) {
        menuComponentList.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
        menuComponentList.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int index) {
        return menuComponentList.get(index);
    }

    @Override
    public void print() {
        // 打印菜单名称
        for(int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);

        // 打印子菜单或者子菜单项名称
        for (MenuComponent component : menuComponentList) {
            component.print();
        }
    }
}

Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能。

菜单项类(类)
/**
 * 菜单项类 : 属于叶子节点
 *
 * @author LiaoYuXing-Ray
 **/
public class MenuItem extends MenuComponent {

    public MenuItem(String name,int level) {
        this.name = name;
        this.level = level;
    }

    public void print() {
        // 打印菜单项的名称
        for(int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);
    }
}

MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。

测试类
/**
 * 测试类
 *
 * @author LiaoYuXing-Ray
 **/
public class Client {
    public static void main(String[] args) {
        // 创建菜单树
        MenuComponent menu1 = new Menu("菜单管理",2);
        menu1.add(new MenuItem("页面访问",3));
        menu1.add(new MenuItem("展开菜单",3));
        menu1.add(new MenuItem("编辑菜单",3));
        menu1.add(new MenuItem("删除菜单",3));
        menu1.add(new MenuItem("新增菜单",3));

        MenuComponent menu2 = new Menu("权限管理",2);
        menu2.add(new MenuItem("页面访问",3));
        menu2.add(new MenuItem("提交保存",3));

        MenuComponent menu3 = new Menu("角色管理",2);
        menu3.add(new MenuItem("页面访问",3));
        menu3.add(new MenuItem("新增角色",3));
        menu3.add(new MenuItem("修改角色",3));


        // 创建一级菜单
        MenuComponent component = new Menu("系统管理",1);
        // 将二级菜单添加到一级菜单中
        component.add(menu1);
        component.add(menu2);
        component.add(menu3);


        // 打印菜单名称(如果有子菜单一块打印)
        component.print();
    }
}

  代码运行结果如下:

	--系统管理
	----菜单管理
	------页面访问
	------展开菜单
	------编辑菜单
	------删除菜单
	------新增菜单
	----权限管理
	------页面访问
	------提交保存
	----角色管理
	------页面访问
	------新增角色
	------修改角色



3 案例二

本案例为菜鸟教程中的案例

3.1 需求

  本案例演示了一个组织中员工的层次结构。我们有一个类 Employee,该类被当作组合模型类。CompositePatternDemo 类使用 Employee 类来添加部门层次结构,并打印所有员工。

在这里插入图片描述

3.2 代码实现

步骤 1

  创建 Employee 类,该类带有 Employee 对象的列表。

Employee.java
import java.util.ArrayList;
import java.util.List;
 
public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List<Employee> subordinates;
 
   //构造函数
   public Employee(String name,String dept, int sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new ArrayList<Employee>();
   }
 
   public void add(Employee e) {
      subordinates.add(e);
   }
 
   public void remove(Employee e) {
      subordinates.remove(e);
   }
 
   public List<Employee> getSubordinates(){
     return subordinates;
   }
 
   public String toString(){
      return ("Employee :[ Name : "+ name 
      +", dept : "+ dept + ", salary :"
      + salary+" ]");
   }   
}

步骤 2

  使用 Employee 类来创建和打印员工的层次结构。

CompositePatternDemo.java
public class CompositePatternDemo {
   public static void main(String[] args) {
      Employee CEO = new Employee("John","CEO", 30000);
 
      Employee headSales = new Employee("Robert","Head Sales", 20000);
 
      Employee headMarketing = new Employee("Michel","Head Marketing", 20000);
 
      Employee clerk1 = new Employee("Laura","Marketing", 10000);
      Employee clerk2 = new Employee("Bob","Marketing", 10000);
 
      Employee salesExecutive1 = new Employee("Richard","Sales", 10000);
      Employee salesExecutive2 = new Employee("Rob","Sales", 10000);
 
      CEO.add(headSales);
      CEO.add(headMarketing);
 
      headSales.add(salesExecutive1);
      headSales.add(salesExecutive2);
 
      headMarketing.add(clerk1);
      headMarketing.add(clerk2);
 
      //打印该组织的所有员工
      System.out.println(CEO); 
      for (Employee headEmployee : CEO.getSubordinates()) {
         System.out.println(headEmployee);
         for (Employee employee : headEmployee.getSubordinates()) {
            System.out.println(employee);
         }
      }        
   }
}

步骤 3

  执行程序,输出结果为:

	Employee :[ Name : John, dept : CEO, salary :30000 ]
	Employee :[ Name : Robert, dept : Head Sales, salary :20000 ]
	Employee :[ Name : Richard, dept : Sales, salary :10000 ]
	Employee :[ Name : Rob, dept : Sales, salary :10000 ]
	Employee :[ Name : Michel, dept : Head Marketing, salary :20000 ]
	Employee :[ Name : Laura, dept : Marketing, salary :10000 ]
	Employee :[ Name : Bob, dept : Marketing, salary :10000 ]



本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~


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

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

相关文章

AI大模型-启航

文章目录 什么是大模型&#xff1f;&#xff08;大体现在参数量巨大&#xff09;大模型将会改变那些行业&#xff08;大模型有哪些作用&#xff1f;&#xff09;如何搞数据训练模型&#xff1f;LangChain带来的技术变革LangChain架构 什么是大模型&#xff1f;&#xff08;大体…

网络编程作业day2

1.将TPC和UDP通信模型各敲两遍 &#xff08;1&#xff09;TPC通信模型&#xff1a; 服务器代码&#xff1a; #include <myhead.h> #define SERVER_IP "192.168.125.136" #define SERVER_PORT 1314 int main(int argc, const char *argv[]) {//1、创建用于监…

信号系统之滤波器比较

比较 1&#xff1a;模拟与数字滤波器 大多数数字信号源自模拟电子设备。**如果需要对信号进行滤波&#xff0c;是在数字化之前使用模拟滤波器&#xff0c;还是在数字化后使用数字滤波器更好&#xff1f;**将通过两个对比来回答问题。 目标是提供 1 kHz的低通滤波器。模拟端是…

八股文打卡day24——数据库(1)

面试题&#xff1a;左连接和右连接的区别&#xff1f; 我的回答&#xff1a; 左连接的SQL语句是&#xff1a;左表 left join 右表 on 连接条件&#xff0c;表示以左表为基础&#xff0c;将左表的的所有记录与右表进行连接。即使右表中没有与左表匹配的记录&#xff0c;左连接…

ROS 2基础概念#2:节点(Node)| ROS 2学习笔记

ROS 2节点简介 节点是执行计算的进程。节点组合在一起形成一个图&#xff08;graph&#xff09;&#xff0c;并使用主题&#xff08;topic&#xff09;、服务&#xff08;service&#xff09;和参数服务器&#xff08;paramter server&#xff09;相互通信。这些节点旨在以细粒…

力扣-H指数

问题 给你一个整数数组 citations &#xff0c;其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维基百科上 h 指数的定义&#xff1a;h 代表“高引用次数” &#xff0c;一名科研人员的 h 指数 是指他&#xff08;她&#xff09…

Tomcat服务部署

1、安装jdk、设置环境变量并测试 第一步&#xff1a;安装jdk 在部署 Tomcat 之前必须安装好 jdk&#xff0c;因为 jdk 是 Tomcat 运行的必要环境。 1. #关闭防火墙 systemctl stop firewalld systemctl disable firewalld setenforce 02. #将安装 Tomcat 所需软件包传到/opt…

数据结构与算法 - 数组与二分查找 + Leetcode典型题

1. 什么是数组 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标下对应的数据。 C中二维数组在地址空间上也是连续的。 需注意&#xff1a; 数组的下标从0开始。数组内存空间的地址是连续的。数组的元素是不能删的&#xff0c…

c#打印BarTend标签提示:具名数据源没有cuckoo*具名数据(解决)

c#打印BarTend标签提示&#xff1a;具名数据源没有cuckoo*具名数据&#xff08;解决&#xff09; 今天咕咕更新打印模板的时候遇到的问题&#xff0c;就是在模版中配置了字段名&#xff0c;但是启动c#应用&#xff0c;后端发送json数据打印的时候c#报错提示&#xff0c;没有在…

ywtool ssh命令

一.SSH免密登陆介绍 这个功能就是通过脚本对本机器和其他机器配置SSH密钥&#xff0c;并将自己的密钥放到其他机器上(确保运维的机器要安全)&#xff0c;这样可以不用输入密码就能登陆&#xff1b;通过scp拷贝文件也不需要输入密码。此功能也可以设置机器root用户只用密钥登陆…

【办公类-22-07】周计划系列(3-2)“信息窗+主题知识(优化)” (2024年调整版本)

作品展示&#xff1a; 背景需求 前文对“2023年2月”的一套信息窗主题知识的文件系列&#xff0c;进行第一次的提取。获得基础模板。 【办公类-22-07】周计划系列&#xff08;3-1&#xff09;“信息窗主题知识&#xff08;提取&#xff09;” &#xff08;2024年调整版本&…

前端-BOM和DOM的区别和用法

首先上图&#xff0c;这是整个JAVASCRIPTD 结构&#xff0c;因此我们可以得出一个关系等式 JavaScript ECMAscript BOM DOMECMAscript&#xff1a; 是一种由 ECMA国际&#xff08;前身为欧洲计算机制造商协会&#xff09;通过 ECMA-262 标准化的脚本程序设计语言&#xff0…

【笔记】深度学习入门:基于Python的理论与实现(五)

卷积神经网络 卷积神经网络(Convolutional Neural Network&#xff0c;CNN) 整体结构 CNN 中新出现了卷积层(Convolution 层)和池化层(Pooling 层)&#xff0c;之前介绍的神经网络中&#xff0c;相邻层的所有神经元之间都有连接&#xff0c;这称为全 连接(fully-connected) …

GPT-SoVITS音色克隆-模型训练步骤

GPT-SoVITS音色克隆-模型训练步骤 GPT-SoVITS模型源码一个简单的TTS后端项目 基于模型部署和训练教程&#xff0c;语雀 模型部署和训练教程 启动模型训练的主页面 1. 切到模型路径 /psycheEpic/GPT-SoVITS进入Python虚拟环境&#xff0c;并挂起执行python脚本 conda activ…

fastAdmin表格列表的功能

更多文章&#xff0c;请关注&#xff1a;fastAdmin后台功能详解 | 夜空中最亮的星 FastAdmin是一款基于ThinkPHP5Bootstrap的极速后台开发框架。优点见开发文档 介绍 - FastAdmin框架文档 - FastAdmin开发文档 在这里上传几张优秀的快速入门图: 一张图解析FastAdmin中的表格列…

【python】Python Turtle绘制流星雨动画效果【附源码】

在这篇技术博客中&#xff0c;我们将学习如何使用 Python 的 Turtle 模块绘制一个流星雨的动画效果。通过简单的代码实现&#xff0c;我们可以在画布上展现出流星闪耀的场景&#xff0c;为视觉带来一丝神秘与美感。 一、效果图&#xff1a; 二、准备工作 &#xff08;1)、导入…

IntelliJ IDEA上svn分支管理和使用

IntelliJ IDEA上svn分支管理和使用 从Subversion下载trunk下的代码 选择项目创建分支 右键 Subversion --> branch or Tag … 选择Repository Location:需要创建的项目 选择Any Location 分支的位置和名字 详细查看截图 切换到分支 选择项目右键Subversion --> Update …

Dockerfile(1) - FROM 指令详解

FROM 指明当前的镜像基于哪个镜像构建dockerfile 必须以 FROM 开头&#xff0c;除了 ARG 命令可以在 FROM 前面 FROM [--platform<platform>] <image> [AS <name>]FROM [--platform<platform>] <image>[:<tag>] [AS <name>]FROM […

全网最新的软件测试面试八股文

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号【互联网杂货铺】&#xff0c;回复 1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧…

如何在Win系统从零开始搭建Z-blog网站,并将本地博客发布到公网可访问

文章目录 1. 前言2. Z-blog网站搭建2.1 XAMPP环境设置2.2 Z-blog安装2.3 Z-blog网页测试2.4 Cpolar安装和注册 3. 本地网页发布3.1. Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 想要成为一个合格的技术宅或程序员&#xff0c;自己搭建网站制作网页是绕…