设计模式_行为型模式 -《责任链模式》

news2024/9/22 3:49:49

设计模式_行为型模式 -《责任链模式》

笔记整理自 黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)

概述

在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。

定义

  • 责任链模式,又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

image-20230109124843959

结构

责任链模式 (Chain of Responsibility Pattern) 主要包含以下角色:

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接(下一个对象的引用)。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

案例实现

现需要开发一个请假流程控制系统。请假 1 天以下的假只需要小组长同意即可;请假 1 天到 3 天的假还需要部门经理同意;请假 3 天到 7 天还需要总经理同意才行。

类图如下:

代码如下:

  • 请假条

    public class LeaveRequest {
        
        private String name; // 姓名
        private int num; // 请假天数
        private String content; // 请假内容
    
        public LeaveRequest(String name, int num, String content) {
            this.name = name;
            this.num = num;
            this.content = content;
        }
    
        public String getName() {
            return name;
        }
    
        public int getNum() {
            return num;
        }
    
        public String getContent() {
            return content;
        }
    }
    
  • 抽象处理者角色

    public abstract class Handler {
        
        protected final static int NUM_ONE = 1;
        protected final static int NUM_THREE = 3;
        protected final static int NUM_SEVEN = 7;
    
        // 该领导处理的请假天数区间
        private int numStart;
        private int numEnd;
    
        // 领导上面还有领导(后继连接,下一个对象的引用)
        private Handler nextHandler;
    
        // 设置请假天数范围 上不封顶
        public Handler(int numStart) {
            this.numStart = numStart;
        }
    
        // 设置请假天数范围
        public Handler(int numStart, int numEnd) {
            this.numStart = numStart;
            this.numEnd = numEnd;
        }
    
        // 设置上级领导
        public void setNextHandler(Handler nextHandler){
            this.nextHandler = nextHandler;
        }
    
        // 提交请假条
        public final void submit(LeaveRequest leave){
            if (0 == this.numStart) {
                return;
            }
    
            // 如果请假天数达到该领导者的处理要求
            if (leave.getNum() >= this.numStart) {
                this.handleLeave(leave);
    
                // 如果还有上级 并且请假天数超过了当前领导的处理范围
                if (this.nextHandler != null && leave.getNum() > numEnd) {
                    this.nextHandler.submit(leave); // 继续往上提交 责任链模式
                } else {
                    System.out.println("流程结束");
                }
            }
        }
    
        // 各级领导处理请假条方法
        protected abstract void handleLeave(LeaveRequest leave);
    }
    
  • 具体处理者角色

    // 具体处理者-小组长
    public class GroupLeader extends Handler {
        public GroupLeader() {
            // 小组长处理1天以下的请假
            super(0, Handler.NUM_ONE);
        }
    
        @Override
        protected void handleLeave(LeaveRequest leave) {
            System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
            System.out.println("小组长审批:同意。");
        }
    }
    
    // 具体处理者-部门经理
    public class Manager extends Handler {
        public Manager() {
            // 部门经理处理1-3天的请假
            super(Handler.NUM_ONE, Handler.NUM_THREE);
        }
    
        @Override
        protected void handleLeave(LeaveRequest leave) {
            System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
            System.out.println("部门经理审批:同意。");
        }
    }
    
    // 具体处理者-总经理
    public class GeneralManager extends Handler {
        public GeneralManager() {
            // 部门经理处理3-7天的请假
            super(Handler.NUM_THREE, Handler.NUM_SEVEN);
        }
    
        @Override
        protected void handleLeave(LeaveRequest leave) {
            System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");
            System.out.println("总经理审批:同意。");
        }
    }
    
  • 测试类

    public class Client {
        public static void main(String[] args) {
            // 请假条来一张
            LeaveRequest leave = new LeaveRequest("张三", 5, "身体不适");
    
            // 各位领导
            GroupLeader groupLeader = new GroupLeader();
            Manager manager = new Manager();
            GeneralManager generalManager = new GeneralManager();
    
            groupLeader.setNextHandler(manager); // 小组长的领导是部门经理
            manager.setNextHandler(generalManager); // 部门经理的领导是总经理
            // 此时责任链已经设置成功了
            // 之所以在这里设置上级领导,是因为可以根据实际需求来更改设置,如果实战中上级领导人都是固定的,则可以移到领导实现类中。
    
            // 提交申请
            groupLeader.submit(leave);
        }
    }
    

    输出

    张三请假5天,身体不适。
    小组长审批:同意
    张三请假5天,身体不适。
    部门经理审批:同意
    张三请假5天,身体不适。
    总经理审批:同意
    流程结束!
    

优缺点

优点

  • 降低了对象之间的耦合度
    • 该模式降低了请求发送者和接收者的耦合度。
  • 增强了系统的可扩展性
    • 可以根据需要增加新的请求处理类,满足开闭原则。
  • 增强了给对象指派职责的灵活性
    • 当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
  • 责任链简化了对象之间的连接
    • 一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  • 责任分担
    • 每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的责任链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 责任链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于责任链的错误设置而导致系统出错,如可能会造成循环调用。

源码解析-JavaWeb

在 JavaWeb 应用开发中,FilterChain 是职责链(过滤器)模式的典型应用,以下是 Filter的 模拟实现分析:

  • 模拟 web 请求 Request 以及 web 响应 Response

    public interface Request {
     
    }
    
    public interface Response {
     
    }
    
  • 模拟 web 过滤器Filter

    public interface Filter {
    	public void doFilter(Request req, Response res, FilterChain c);
    }
    
  • 模拟实现具体过滤器

    public class FirstFilter implements Filter {
        @Override
        public void doFilter(Request request, Response response, FilterChain chain) {
    
            System.out.println("过滤器1 前置处理");
    
            // 先执行所有request再倒序执行所有response
            chain.doFilter(request, response);
    
            System.out.println("过滤器1 后置处理");
        }
    }
    
    public class SecondFilter implements Filter {
        @Override
        public void doFilter(Request request, Response response, FilterChain chain) {
    
            System.out.println("过滤器2 前置处理");
    
            // 先执行所有request再倒序执行所有response
            chain.doFilter(request, response);
    
            System.out.println("过滤器2 后置处理");
        }
    }
    
  • 模拟实现过滤器链 FilterChain

    public class FilterChain {
    
        private List<Filter> filters = new ArrayList<Filter>();
    
        private int index = 0;
    
        // 创建过滤器链对象 -> 链式调用
        public FilterChain addFilter(Filter filter) {
            this.filters.add(filter);
            return this;
        }
    
        public void doFilter(Request request, Response response) {
            if (index == filters.size()) {
                return;
            }
            Filter filter = filters.get(index);
            index++;
            filter.doFilter(request, response, this);
        }
    }
    
  • 测试类

    public class Client {
        public static void main(String[] args) {
            Request  req = null;
            Response res = null;
    
            FilterChain filterChain = new FilterChain();
            filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter());
            filterChain.doFilter(req, res);
        }
    }
    

    输出

    过滤器1 前置处理
    过滤器2 前置处理
    过滤器2 后置处理
    过滤器1 后置处理
    

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

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

相关文章

windows最小化安装mysql8

第一步&#xff1a;下载 从官网下载&#xff0c;https://dev.mysql.com/downloads/mysql 第二步&#xff1a;安装 下载后解压到目录即可。 我这里解压到D:\MYSQL\mysql-8.0.32-winx64\mysql-8.0.32-winx64 第三步&#xff1a;初始化配置 1、添加系统变量 在系统变量PATH后…

【C++】从0到1入门C++编程学习笔记 - 基础入门篇:数组

文章目录一、概述二、一维数组2.1 一维数组定义方式2.2 一维数组数组名2.3 C 实现冒泡排序三、二维数组3.1 二维数组定义方式3.2 二维数组数组名3.3 二维数组应用案例一、概述 所谓数组&#xff0c;就是一个集合&#xff0c;里面存放了相同类型的数据元素 特点1&#xff1a;数…

(15)go-micro微服务main.go开发

文章目录一 导包二 配置中心三 注册中心四 zap日志初始化五 初始化Mysql数据库六 初始化Redis连接七 注册服务八 初始化服务九 注册 handle十 启动服务十一 main.go全部代码十二 最后一 导包 import (micro2 "account/common/micro""account/config/logger&quo…

day17|530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输出&#xff1a;1 示例 2&#xff1…

Redis持久化——AOF机制详解

在运行情况下&#xff0c;Redis 以数据结构的形式将数据维持在内存中&#xff0c;为了让这些数据在 Redis 重启之后仍然可用&#xff0c;需要将数据写入持久存储 持久化是指将数据写入持久存储&#xff0c;例如固态磁盘(SSD) Redis 提供了一系列持久化选项。这些包括&#xff1…

[GYCTF2020]Blacklist(堆叠注入)

目录 信息收集 堆叠注入 payload 总结 信息收集 观察url变化是get请求的SQL注入题目 判断注入类型 1和1"回显一样 1回显报错 1 or 11# array(2) {[0]>string(1) "1"[1]>string(7) "hahahah" }array(2) {[0]>string(1) "2"[…

【C语言进阶】最常用的库函数大全——从入门到精通

目录 一.字符串函数 1.strlen——求字符串长度 2.长度不受限制的字符串函数 a.strcpy——字符串拷贝 b.strcat——追加字符串 c.strcmp——字符串比较 3.长度受限制的字符串函数——strncpy,strncat,strncmp 4.字符串查找 a.strstr——判断是否为子字符串 b.strtok—…

Java网络编程(2)

关于网络编程上一章内容&#xff0c;可以参考&#xff1a; https://blog.csdn.net/Raine_Yang/article/details/128697335?spm1001.2014.3001.5501 使用服务器处理多个客户端 一般来说&#xff0c;同一服务器要持续运行处理多个客户端的请求。我们可以为每一个客户端请求分配…

@Scope 用法

参考&#xff1a;Scope注解 详细讲解及示例 官方文档 When used as a type-level annotation in conjunction with Component, Scope indicates the name of a scope to use for instances of the annotated type. When used as a method-level annotation in conjunction w…

【NI Multisim 14.0虚拟仪器设计——放置虚拟仪器仪表(示波器)】

目录 序言 &#x1f34d;放置虚拟仪器仪表 &#x1f349;示波器 &#x1f34a;&#x1f34a;1.“时基”选项组 &#xff08;1&#xff09;标度 &#xff08;2&#xff09;X轴位移 &#xff08;3&#xff09;显示方式选择 &#x1f34a;&#x1f34a;2.“通道”选项组 …

函数指针与回调函数详解

目录1.函数指针2.函数指针数组3.指向函数指针数组的指针4.回调函数1.函数指针 前面我们学的&#xff1a; 整形指针是指向整形的指针字符指针是指向字符的指针数组指针是指向数组的指针 所以函数指针就是指向函数的指针 假如有一个int类型变量a&#xff0c;要取它的地址就是…

【Linux】Linux软件包管理器与Linux编辑器

文章目录&#x1f3aa; Linux软件包管理器&#x1f680; 1.yum基本介绍&#x1f680; 2.yum基本命令&#x1f680; 3.关于rzsz工具的安装与使用⭐3.1 rzsz工具介绍⭐3.2 rzsz工具安装⭐3.3 rzsz工具使用&#x1f3aa; Linux编辑器&#x1f680; 1.vim常用三种模式&#x1f680;…

事务管理-spring

什么是事务 - 事务是由N步数据库操作序列组成的逻辑执行单元&#xff0c;这系列操作要么全执行&#xff0c;要么全放弃执行。 • 事务的特性&#xff08;ACID&#xff09; - 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务是应用中不可再分的最小执行体。 - 一致…

MySQL使用索引的最佳指南

MySQL使用索引的最佳指南1.选择合适的字段创建索引2.尽可能的考虑建立联合索引而不是单列索引3.注意避免冗余索引4.考虑在字符串类型的字段上使用前缀索引代替普通索引5.索引失效的情况1.选择合适的字段创建索引 不为 NULL 的字段 &#xff1a;索引字段的数据应该尽量不为 NUL…

java面向对象,全是对象,这么多对象2023015

面向对象&#xff08;一遍一遍的领悟&#xff09; Java支持面向对象的三大特征&#xff1a;封装、继承和多态&#xff0c; Java提供 了private、protected和public三个访问控制修饰符来实现良好的封装&#xff0c;提供了extends关键字来让子类继承父类&#xff0c;子类继承父类…

人工智能图像形状检测算法

博主简介 博主是一名大二学生&#xff0c;主攻人工智能研究。感谢让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;c&#xff0c;Python&#xff0c;爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主&#xff0c;博主会继续更新的&#xff0c…

Redis下载安装与配置(linux)

一、Redis下载与安装 1.下载安装包 官网下载地址&#xff1a;Download | Redis 点击"Download 7.0.7"&#xff0c;即可进行下载。 2.将安装包上传至服务器 2.1将安装包上传至/usr/local目录并解压 cd /usr/local lstar -zxvf redis-7.0.7.tar.gz2.2删除安装包 r…

第二天总结 之 商品类型管理界面的实现 之 添加和修改操作 的实现

添加和修改操作 页面跳转问题 点击修改按钮时 跳转的路径 如下 点击添加按钮时 跳转的路径如下 通过这两张图片 不难发现 跳转的是同一个jsp 但是添加操作 是不带id跳转 而修改操作是带着id跳转 所以在其 跳转的页面add_goods_type.jsp页面中 有一个这样的判断 如果没有id…

Linux常用命令——tmux命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) tmux Tmux是一个优秀的终端复用软件&#xff0c;类似GNU Screen&#xff0c;但来自于OpenBSD&#xff0c;采用BSD授权。 补充说明 使用它最直观的好处就是&#xff0c;通过一个终端登录远程主机并运行tmux后&a…

2022年HarmonyOS/OpenHarmony生态观察

一、鸿蒙生态世界快速构建升级中 HarmonyOS鸿蒙2019年正式面世&#xff0c;当时消费者只能在华为的智慧屏上体验&#xff1b;2020年&#xff0c;鸿蒙智联-华为面向智能硬件生态伙伴全新品牌和开放平台发布&#xff1b;2021年&#xff0c;智能手机等多种终端全面搭载HarmonyOS2…