设计模式之职责链模式(Chain of Responsibility Pattern)

1.概念

 职责链模式(Chain of Responsibility Pattern):避免将请求发送者与接收者耦合在一起,让多个对象都有机会接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式

2.结构

职责链模式结构的核心在于引入了一个抽象处理者
在这里插入图片描述

从图中可以看出,在职责链模式结构图中包含以下两个角色:
 (1)Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类。由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每个处理者的下家还是一个处理者,因此在抽象处理者中定义一个抽象处理者类型的对象(结构图中的successor),作为其对下家的引用。通过该引用,处理者可以连成一条链。
 (2)ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求。在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,可以访问链中下一个对象,达到请求转发的效果。
 在职责链模式里,每个对象对其下家的引用连接起来形成一条链。请求在这个链上传递,直到链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配职责。

3.典型代码

职责链模式的核心在于抽象处理者类的设计,抽象处理者类的典型代码如下:

abstract class Handler {//维持对下家的引用protected Handler successor;public void setSuccessor(Handler successor) {this.successor = successor;    }public abstract void handleRequest(String request);
}

在上述代码中,抽象处理者定义了对下家的引用对象,以便将请求转发给下家。该对象的访问符可设为 protected,在其子类中可以使用。

具体处理者是抽象处理者的子类,它有两大作用:

  • 处理请求,不同的具体处理者以不同的形式实现抽象请求处理方法 handleRequest();
  • 转发请求,如果该请求超出了当前处理者的处理范围,可以将该请求转发给下家。

具体处理者类的典型代码如下:

class ConcreteHandler extends Handler {public void handleRequest(String request) {if (请求满足条件) {//处理请求        } else {this.successor.handleRequest(request);  //转发请求}}
}

4.案例分析一:采购单审批系统

某一采购单审批系统是分级进行的,即根据采购金额的不同由不同层次的主管人员来审批。

主任可以审批5万元以下的采购单,副董事长可以审批 [5, 10) 万元的采购单,董事长可以审批 [10, 50) 万元的采购单,50万元及以上的采购单就需要开董事会讨论决定。
在这里插入图片描述

由于每个职位的审批者都有下家(除了董事会),且他们的行为是有共通性的,都涉及将审批转发给后继。因此设计一个审批者类作为抽象处理者:

//审批者类: 抽象处理者
abstract class Approver {protected Approver successor;  //定义后继对象protected String name;  //审批者姓名public Approver(String name) {this.name = name;}//设置后继者public void setSuccessor(Approver successor) {this.successor = successor;}public abstract void processRequest(PurchaseRequest request);
}

然后各个职位的作为具体处理者,要实现抽象处理者:

//主任: 具体处理者
class Director extends Approver{public Director(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest request) {if (request.getAmount() < 50000) {System.out.println(MessageFormat.format("主任 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",this.name, request.getNumber(), request.getAmount(), request.getPurpose()));} else {this.successor.processRequest(request);  //转发请求}}
}//副董事长:具体处理类
class VicePresident extends Approver{public VicePresident(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest request) {if (request.getAmount() < 100000) {System.out.println(MessageFormat.format("副董事长 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",this.name, request.getNumber(), request.getAmount(), request.getPurpose()));} else {this.successor.processRequest(request);}}
}//董事长类:具体处理者
class President extends Approver{public President(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest request) {if (request.getAmount() < 500000) {System.out.println(MessageFormat.format("董事长 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",this.name, request.getNumber(), request.getAmount(), request.getPurpose()));} else {this.successor.processRequest(request);}}
}//董事会类:具体处理者
class Congress extends Approver{public Congress(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest request) {System.out.println(MessageFormat.format("召开董事会 审批采购单:{0}, 金额:{1}元, 采购目的:{2}。",request.getNumber(), request.getAmount(), request.getPurpose()));}
}

再定义一个采购单类,作为需要被审批的目标:

//采购单: 请求类
class PurchaseRequest {private double amount;  //采购金额private int number;  //采购单编号private String purpose;  //采购目的public PurchaseRequest(double amount, int number, String purpose) {this.amount = amount;this.number = number;this.purpose = purpose;}public double getAmount() {return amount;}public void setAmount(double amount) {this.amount = amount;}public int getNumber() {return number;}public void setNumber(int number) {this.number = number;}public String getPurpose() {return purpose;}public void setPurpose(String purpose) {this.purpose = purpose;}
}

编写客户端测试代码:

class Client {public static void main(String[] args) {Approver kangXi, yongZheng, qianLong, hanLinYuan;kangXi = new Director("康熙");yongZheng = new VicePresident("雍正");qianLong = new VicePresident("乾隆");hanLinYuan = new Congress("翰林院");//创建职责链kangXi.setSuccessor(yongZheng);yongZheng.setSuccessor(qianLong);qianLong.setSuccessor(hanLinYuan);//创建采购单PurchaseRequest pr1 = new PurchaseRequest(45000, 10001, "购买刀");kangXi.processRequest(pr1);PurchaseRequest pr2 = new PurchaseRequest(60000, 10002, "购买枪");kangXi.processRequest(pr2);PurchaseRequest pr3 = new PurchaseRequest(160000, 10003, "购买火炮");kangXi.processRequest(pr3);PurchaseRequest pr4 = new PurchaseRequest(800000, 10004, "购买军舰");kangXi.processRequest(pr4);}
}

编译并运行程序,输出结果如下:

主任 康熙 审批采购单:10,001, 金额:45,000元, 采购目的:购买刀。
副董事长 雍正 审批采购单:10,002, 金额:60,000元, 采购目的:购买枪。
董事长 乾隆 审批采购单:10,003, 金额:160,000元, 采购目的:购买火炮。
召开董事会 审批采购单:10,004, 金额:800,000元, 采购目的:购买军舰。

5.案例分析二:班级任务处理

下面来设计一个和上面有些不同的案例,这样可以扩展我们对职责链模式的理解,以及它所能应用的场景。

  • 第1点不同:处理者的请求方法带有返回值,可以反馈信息给上家;
  • 第2点不同:《采购单审批系统》案例是先处理请求再转发请求,而该《班级任务处理》案例是先转发请求再处理请求

学校会派发一些任务给班级进行处理,这些类型包括 one, two, three, four 等等类型,班主任可以处理 one, two, three 这三种类型的任务,班长可以处理 one, two 这两种类型的任务,学习委员可以处理 one 这种类型的任务。班主任在收到任务时,会先将任务交给班长处理,如果下家处理不了,班主任再自己处理;班长在收到任务时,会先将任务交给学委处理,如果下家处理不了,班长再自己处理;学委收到任务时,如果能则自己处理。且他们处理不了时,都会向上反馈。

下面设计Handler类作为抽象处理者:
由于自己以及自己的下家并不一定能处理某些班级任务,存在向上反馈的情况,因此处理者的请求方法需要有boolean型返回值,用于告诉上家是否能处理该任务。

abstract class Handler {protected Handler successor;  //定义后继对象public void setSuccessor(Handler successor) {this.successor = successor;}public abstract boolean handleRequest(String taskName);
}

下面分别将班主任、班长、学习委员设计为具体处理者。
班主任默认先将任务给班长处理,班长默认先将任务给学习委员处理,学习委员只能处理 one 类型的任务,处理不了则向上返回 false;
班长如果收到的反馈为false,则自己处理,他只能处理 one, two 类型的任务,处理不了则向上返回 false;
班主任如果收到的反馈为false,则自己处理,他只能处理 one, two, three 类型的任务,处理不了则向上返回false。

//班主任
class HeadTeacher extends Handler{@Overridepublic boolean handleRequest(String taskName) {boolean handled = successor.handleRequest(taskName);if (handled) {return true;}if (taskName.equals("one") || taskName.equals("two") || taskName.equals("three")) {System.out.println("班主任处理了该事务");return true;}return false;}
}//班长
class Monitor extends Handler{@Overridepublic boolean handleRequest(String taskName) {boolean handled = successor.handleRequest(taskName);if (handled) {return true;}if (taskName.equals("one") || taskName.equals("two")) {System.out.println("班长处理了该事务");return true;}return false;}
}//学习委员
class StudyCommissary extends Handler{@Overridepublic boolean handleRequest(String taskName) {boolean handled;if (successor == null) {  //注意学习委员可能没有下家,所以这里判一下是否为空handled = false;} else {handled = successor.handleRequest(taskName);}if (handled) {return true;}if (taskName.equals("one")) {System.out.println("学习委员处理了该事务");return true;}return false;}
}

编写客户端测试代码:
分别给每个职位设置下家,不过注意学习委员没有下家。
还有如果出现班主任也处理不了的任务,则打印一行日志进行说明

public class SchoolClient {public static void main(String[] args) {Handler headTeacher, monitor, studyCommissary;headTeacher = new HeadTeacher();monitor = new Monitor();studyCommissary = new StudyCommissary();headTeacher.setSuccessor(monitor);monitor.setSuccessor(studyCommissary);studyCommissary.setSuccessor(null);  //没有下一职责人startRequest(headTeacher, "one");startRequest(headTeacher, "two");startRequest(headTeacher, "three");startRequest(headTeacher, "four");}private static void startRequest(Handler headTeacher, String taskName) {if (! headTeacher.handleRequest(taskName)) {System.out.println("该班级处理不了此类任务!");}}
}

编译并运行程序,输出结果如下:

学习委员处理了该事务
班长处理了该事务
班主任处理了该事务
该班级处理不了此类任务!

6.适用场景

在以下情况下可以考虑适用职责链模式:

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定。客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的
  • 在不明确指定接收者的情况下,向多个对象中的一个提交请求
  • 可动态指定一组对象处理请求。客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序


参考书籍:
《设计模式的艺术》——刘伟

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

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

相关文章

「Pytorch」CopyPaste 数据增强

数据增广是提升模型泛化能力重要的手段之一&#xff0c;CopyPaste 是一种新颖的数据增强技巧&#xff0c;已经在目标检测和实例分割任务中验证了有效性。利用 CopyPaste&#xff0c;可以合成文本实例来平衡训练图像中的正负样本之间的比例。相比而言&#xff0c;传统图像旋转、…

HTML5文本标签、图像标签、超链接

一、文本样式标签 字体样式标签&#xff1a; 加粗&#xff1a;<strong>…</strong> 斜体&#xff1a; < em >…</ em> eg&#xff1a; <h3>徐志摩人物简介</h3> <p> <strong>1910</strong>年入杭州学堂<br/> &l…

java之循环练习题

思路分析&#xff1a; 代码&#xff1a; public static void main(String[] args) {int sum0;for (int i1;i<100;i){for (int j1;j<i;j) {sum j;}}System.out.println(sum);} 结果为&#xff1a;

uniapp上架到appstore遇到的问题

1、appstore在美国审核&#xff0c;需要把服务器接口的国外访问权限放开 2、登陆部分 a、审核时只能有密码登陆&#xff0c;可以通过接口响应参数将其他登陆方式暂时隐藏&#xff0c;审核成功后放开即可 b、需要有账号注销功能 3、使用照相机和相册功能时需要写清楚描述文案

制作问卷表单二维码的方法,扫码登记信息更快捷

为了更好地收集用户信息&#xff0c;现在很多场景下会使用生成二维码的方式&#xff0c;让用户可以扫码自行填写相关信息&#xff0c;从而提高获取信息的效率以及填写数据的便捷性。那么用于收集用户数据的表单二维码是如何生成的呢&#xff1f;其实方法很简单&#xff0c;现在…

leetcode 283.移动零

leetcode 283.移动零 自己刷题并且进行记录一下 题解 c class Solution { public:void moveZeroes(vector<int>& nums) {int count 0;for (int i 0; i < nums.size(); i) {if(nums[i] ! 0) {nums[count] nums[i];if (count !i) {nums[i] 0;}count;}}} };

使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击-飞船动画(三)

文章目录 开发思路飞船尾焰左右移动动画唯一名称的添加 使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击&#xff08;一&#xff09; 使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击-激光组件&#xff08;二&#xff09; 开发思路 整体开发还是基于组件的思维。相比…

录音的内容怎么做二维码?支持多种音频格式使用的制作技巧

怎么把录制的音频文件做成二维码呢&#xff1f;现在用二维码来存储内容是一种很常用的方式&#xff0c;让其他人扫描二维码来查看内容&#xff0c;从而提升内容传输的速度。比如现在很多人会将音频生成二维码&#xff0c;其他人可以通过扫码在手机上播放音频内容&#xff0c;那…

rfid资产管理系统解决方案 rfid固定资产管理系统建设方案

在现代化的仓库储备中&#xff0c;仅仅完成对货物进出的简单批次处理已经不再足够&#xff0c;对库内货品的种类、数量、生产属性、垛位等信息的清晰记录变得至关重要。然而&#xff0c;传统的资产管理方式如条形码在长期使用中逐渐暴露出不耐脏、数据存储量小、读取间隔短、不…

【Linux进阶】文件和目录的默认权限与隐藏权限

1.文件默认权限&#xff1a;umask OK&#xff0c;那么现在我们知道如何建立或是改变一个目录或文件的属性了&#xff0c;不过&#xff0c;你知道当你建立一个新的文件或目录时&#xff0c;它的默认权限会是什么吗&#xff1f; 呵呵&#xff0c;那就与umask这个玩意儿有关了&…

html——VSCode的使用

快捷键 快速生成标签&#xff1a;标签名tab 保存文件&#xff1a;CtrlS 设置自动保存【文件】→【自动保存】 快速查看网页效果&#xff1a;右击→Open in Default Browser 快捷键&#xff1a;altb 注意&#xff1a;必须安装了open in brows…

批量下载手机中APP程序中文件

需求 利用 adb pull 下载手机中app的某目录 adb pull 命令本身不支持直接下载整个目录&#xff08;文件夹&#xff09;及其所有子目录和文件作为一个单一的操作。但是&#xff0c;可以通过一些方法来间接实现这一目的。 方法 1. 首先将要下载的目录进行 tar 打包 # 在 And…

【软件测试】 1+X初级 功能测试试题

岗位管理模块需求说明书 人资管理员登录系统&#xff0c;在“岗位管理”模块&#xff0c;可以对系统中岗位数据进行维 护。岗位管理需求包括用户&#xff08;UI&#xff09;页面、业务规则两部分。 UI 页面 岗位管理&#xff1a;列表页 岗位管理&#xff1a;“添加岗位”窗口 …

Docker 使用基础(1)—镜像仓库

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;秒針を噛む—ずっと真夜中でいいのに。 0:34━━━━━━️&#x1f49f;──────── 4:20 &#x1f504; ◀️ ⏸ …

ST7789 linux4.x驱动

文章目录 ST7789 linux4.x驱动设备树配置驱动程序编译驱动测试驱动 ST7789 linux4.x驱动 设备树配置 pinctrl_ecspi2_cs_1: ecspi2_cs_grp-1 {fsl,pins <MX6UL_PAD_CSI_DATA01__GPIO4_IO22 0x40017059>; };pinctrl_ecspi2_1: escpi2grp {fsl,pins <MX6UL_PAD_CSI_…

数字系统与进制转换

数字系统 数字逻辑是计算机科学的基础&#xff0c;它研究的是如何通过逻辑门电路&#xff08;与门、或门、非门等&#xff09;实现各种逻辑功能。数字系统则是由数字逻辑电路组成的系统&#xff0c;可以实现各种复杂的运算和控制功能。在计算机科学中&#xff0c;数字逻辑和数…

项目记录:C语言学生成绩排名程序

一个很简单的小项目&#xff0c;大一的学生作业。我简单介绍一下相关功能和代码之类的吧~ 本来题目不强制要求菜单的&#xff0c;我有点强迫症加了菜单。 【1】题目&#xff1a; 一个班40名学生&#xff0c;期末有10门课程成绩&#xff0c;要求计算并输出如下数据&#xff1a; …

Xilinx Vitis 2020工程源目录修改

目录 1 背景2 分析3 解决4 使用4.1 修改路径4.2 编译工程4.2.1 清理工程4.2.2 编译工程 1 背景 Xilinx Vitis可以做standalone程序开发,不过其工程中使用的路径为绝对路径。工程更换位置后编译将会显示错误。例如&#xff1a;源目录为D:/work,复制到同事电脑上放到C:/work(同事…

注册中心组成结构和基本原理解析

假如你正在设计和开发一个分布式服务系统&#xff0c;系统中存在一批能够独立运行的服务&#xff0c;而在部署上也采用了集群模式以防止出现单点故障。显然&#xff0c;对于一个完整的业务系统而言&#xff0c;这些服务之间需要相互调用并形成复杂的访问链路&#xff0c;一种可…

HTML(29)——立体呈现

作用&#xff1a;设置元素的子元素是位于3D空间中还是平面中 属性名&#xff1a;transform-style 属性值&#xff1a; flat&#xff1a;子级处于平面中preserve-3d:子级处于3D空间 步骤&#xff1a; 父级元素添加 transform-style:preserve-3d 子级定位调整子盒子的位置&a…