责任链模式(Chain of Responsibility)

别名

命令链(Chain of Command)。

定义

责任链是一种行为设计模式允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者

前言

1. 问题

假如你正在开发一个在线订购系统。你希望对系统访问进行限制,只允许认证用户创建订单。此外,拥有管理权限的用户也拥有所有订单的完全访问权限。

简单规划后,你会意识到这些检查必须依次进行。只要接收到包含用户凭据的请求,应用程序就可尝试对进入系统的用户进行认证。但如果由于用户凭据不正确而导致认证失败,那就没有必要进行后续检查了。

随着功能不断迭代,检查代码逐渐变得越来越混乱,修改某个检查步骤有时会影响其他的检查步骤。最糟糕的是,当你希望复用这些检查步骤来保护其他系统组件时,你只能复制部分代码,因为这些组件只需要部分而非全部的检查步骤。

系统会变得让人非常费解,而且其维护成本也会激增。你在艰难地和这些代码共处一段时间后,有一天终于决定对整个系统进行重构。

2. 解决方案

与许多其他行为设计模式一样,「责任链模式」会将特定行为转换为被称作处理者的独立对象。在上述示例中,每个检查步骤都可被抽取为仅有单个方法的类,并执行检查操作。请求及其数据则会被作为参数传递给该方法。

模式建议你将这些处理者连成一条链。链上的每个处理者都有一个成员变量来保存对于下一处理者的引用。除了处理请求外,处理者还负责沿着链传递请求。请求会在链上移动,直至所有处理者都有机会对其进行处理。

最重要的是:处理者可以决定不再沿着链传递请求,这可高 效地取消所有后续处理步骤。

在我们的订购系统示例中,处理者会在进行请求处理工作后决定是否继续沿着链传递请求。如果请求中包含正确的数据,所有处理者都将执行自己的主要行为,无论该行为是身份验证还是数据缓存。

不过还有一种稍微不同的方式(也是更经典一种),那就是处理者接收到请求后自行决定是否能够对其进行处理。如果自己能够处理,处理者就不再继续传递请求。因此在这种情况下,每个请求要么最多有一个处理者对其进行处理,要么没有任何处理者对其进行处理。在处理图形用户界面元素栈中的事件时,这种方式非常常见。

所有处理者类均实现同一接口是关键所在。每个具体处理者仅关心下一个包含 execute 执行 方法的处理者。 这样一来,你就可以在运行时使用不同的处理者来创建链,而无需将相关代码与处理者的具体类进行耦合。

结构

  1. 处理者(Handler)声明了所有具体处理者的通用接口。该接口通常仅包含单个方法用于请求处理,但有时其还会包含一个设置链上下个处理者的方法。
  2. 基础处理者(Base Handler)是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中。通常情况下,该类中定义了一个保存对于下个处理者引用的成员变量。客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。该类还可以实现默认的处理行为:确定下个处理者存在后再将请求传递给它。
  3. 具体处理者(Concrete Handlers)包含处理请求的实际代码。每个处理者接收到请求后,都必须决定是否进行处理,以及是否沿着链传递请求。处理者通常是独立且不可变的,需要通过构造函数一次性地获得所有必要地数据。
  4. 客户端(Client)可根据程序逻辑一次性或者动态地生成链。值得注意的是,请求可发送给链上的任意一个处理者,而非必须是第一个处理者。

适用场景

  • 当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时,可以使用责任链模式。

该模式能将多个处理者连接成一条链。接收到请求后,它会“询问”每个处理者是否能够对其进行处理。这样所有处理者都有机会来处理请求。

  • 当必须按顺序执行多个处理者时,可以使用该模式。

无论你以何种顺序将处理者连接成一条链,所有请求都会严格按照顺序通过链上的处理者。

  • 如果所需处理者及其顺序必须在运行时进行改变,可以使用责任链模式。该类需要有一个成员变量来存储指向链上下个处理者的引用。你可以将其设置为不可变类。但如果你打算在运行时对链进行改变,则需要定义一个设定方法来修改引用成员变量的值。

如果在处理者类中有对引用成员变量的设定方法,你将能动态地插入和移除处理者,或者改变其顺序。

实现方式

  1. 声明处理者接口并描述请求处理方法的签名。确定客户端如何将请求数据传递给方法。最灵活的方式是将请求转换为对象,然后将其以参数的形式传递给处理函数。
  2. 为了在具体处理者中消除重复的样本代码,你可以根据处理者接口创建抽象处理者基类。该类需要有一个成员变量来存储指向链上下个处理者的引用。你可以将其设置为不可变类。但如果你打算在运行时对链进行改变,则需要定义一个设定方法来修改引用成员变量的值。为了使用方便,你还可以实现处理方法的默认行为。如果还有剩余对象,该方法会将请求传递给下个对象。具体处理者还能够通过调用父对象的方法来使用这一行为。
  3. 依次创建具体处理者子类并实现其处理方法。每个处理者在 接收到请求后都必须做出两个决定: a.是否自行处理这个请求是否将该请求沿着链进行传递。
  4. 客户端可以自行组装链,或者从其他对象处获得预先组装好的链。在后一种情况下,你必须实现工厂类以根据配置或环境设置来创建链。
  5. 客户端可以触发链中的任意处理者,而不仅仅是第一个。请求将通过链进行传递,直至某个处理者拒绝继续传递,或者请求到达链尾。
  6. 由于链的动态性,客户端需要准备好处理以下情况:
    1. 链中可能只有单个链接。
    2. 部分请求可能无法到达链尾。
    3. 其他请求可能直到链尾都未被处理。

优点

  • 你可以控制请求处理的顺序。
  • 单一职责原则。你可对发起操作和执行操作的类进行解耦。
  • 开闭原则。你可以在不更改现有代码的情况下在程序中新增 处理者。

缺点

部分请求可能未被处理。

BaseHandler.hpp

#ifndef F52BFA24_4290_4919_A52A_DD89BBD73E6A
#define F52BFA24_4290_4919_A52A_DD89BBD73E6A#include <string>
#include "Handler.hpp"
class BaseApprover : public ApproverInterface {public:BaseApprover(double mpa, std::string n) : max_processible_amount_(mpa), name_(n), superior_(nullptr) {}// 设置上级void setSuperior(ApproverInterface* superior) {superior_ = superior;}// 处理票据void handleRequest(double amount) {// 可处理时直接处理即可if (amount <= max_processible_amount_) {printf("%s处理了该票据, 票据面额:%f\n", name_.c_str(), amount);return;}// 无法处理时移交给上级if (superior_ != nullptr) {printf("%s无权处理, 转交上级...\n", name_.c_str());superior_->handleRequest(amount);return;}// 最上级依然无法处理时报错printf("无人有权限处理该票据, 票据金额:%f\n", amount);}private:double max_processible_amount_;  // 可处理的最大面额std::string name_;ApproverInterface* superior_;
};#endif /* F52BFA24_4290_4919_A52A_DD89BBD73E6A */

Handler.hpp

#ifndef C299C914_BF51_4129_AB44_E94EFF3E1C1E
#define C299C914_BF51_4129_AB44_E94EFF3E1C1Eclass   ApproverInterface
{private:public:virtual void setSuperior(ApproverInterface * superior) = 0;virtual void handleRequest(double amount) = 0;};#endif /* C299C914_BF51_4129_AB44_E94EFF3E1C1E */

ConcreteHandler.hpp

#ifndef CB6528AE_058B_4392_A96A_463A522CD622
#define CB6528AE_058B_4392_A96A_463A522CD622#include <string>
#include <cstdio>
#include "BaseHandler.hpp"// 具体处理者: 组长(仅处理面额<=10的票据)
class GroupLeader : public BaseApprover {public:explicit GroupLeader(std::string name) : BaseApprover(10, name) {}
};// 具体处理者: 经理(仅处理面额<=100的票据)
class Manager : public BaseApprover {public:explicit Manager(std::string name) : BaseApprover(100, name) {}
};// 具体处理者: 老板(仅处理面额<=1000的票据)
class Boss : public BaseApprover {public:explicit Boss(std::string name) : BaseApprover(1000, name) {}
};#endif /* CB6528AE_058B_4392_A96A_463A522CD622 */

main.cpp

#include "ConcreteHandler.hpp"int main() {// 请求处理者: 组长、经理和老板GroupLeader* group_leader = new GroupLeader("张组长");Manager* manager = new Manager("王经理");Boss* boss = new Boss("李老板");// 设置上级group_leader->setSuperior(manager);manager->setSuperior(boss);// 不同面额的票据统一先交给组长审批group_leader->handleRequest(8);group_leader->handleRequest(88);group_leader->handleRequest(888);group_leader->handleRequest(8888);delete group_leader;delete manager;delete boss;return 0;
}

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

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

相关文章

Linux基于thundersvm使用GPU对svm进行加速

Linux基于thundersvm使用GPU对svm进行加速 文章目录 Linux基于thundersvm使用GPU对svm进行加速下载方法pip快速下载命令 普通下载命令问题解决方法以下操作需要使用sudo权限 使用thundersvm调用方式 在代码中的使用训练代码更改更改内容 加载模型同样需要导入thundersvm更改内容…

Flutter 组件(二)文本 与 输入框组件

Flutter开发笔记 Flutter 组件&#xff08;二&#xff09;文本 与 输入框组件 - 文章信息 - Author: Jack Lee (jcLee95) Visit me at: https://jclee95.blog.csdn.netEmail: 291148484163.com. Shenzhen ChineAddress of this article:https://blog.csdn.net/qq_28550263/art…

QT学习笔记4--自定义信号的槽

逻辑&#xff1a;下课后&#xff0c;老师饿了&#xff0c;学生请吃饭。 使用connect函数连接自定义的信号和槽函数。 创建类 信号 #ifndef TEACHER_H #define TEACHER_H#include <QObject>class teacher : public QObject {Q_OBJECT public:explicit teacher(QObjec…

【网络管理发展】网络杂谈(12)之网络管理未来发展趋势

涉及知识点 网络管理未来的发展方向&#xff0c;网络管理未来的发展趋势&#xff0c;个人闲谈网络管理未来发展&#xff0c;网络管理技术现状&#xff0c;应用服务供应商&#xff08;ASP&#xff09;&#xff0c;网络的远程管理&#xff0c;人工智能与未来。 原创于&#xff1…

爬虫工具-替换js文件ReRes插件/Gores插件

目录 一、ReRes插件二、Gores插件 一、ReRes插件 用途&#xff1a;爬虫逆向过程中一些文件需要替换时 ① 原始网站js文件有无限debugger&#xff0c;复制原始网站js文件&#xff0c;删掉无限debugger相关代码保存为新的js文件&#xff1b;用ReRes插件进行替换② 原始网站js文件…

【分布式存储】聊聊共识和一致性

在分布式存储系统中&#xff0c;对于提高性能、可用性、可拓展性来说都有相关机制可以保证&#xff0c;比如复制、切片等&#xff0c;但是一旦涉及到分布式系统中选主的问题&#xff0c;就比较难&#xff0c;因为网络是不可靠的&#xff0c;并且可能还有拜占庭将军问题。所以如…

线程安全的集合类

目录 一、线程安全的集合类 1.1、多线程环境下使用ArrayList 1.2、多线程使用队列 1.3、多线程环境使用哈希表 1.3.1、Hashtable 1.3.2、ConcurrentHashMap 一、线程安全的集合类 在多线程的环境下&#xff0c;多个线程对同一个共享变量进行写操作的时候&#xff0c;有可…

chatGPT流式回复是怎么实现的

chatGPT流式回复是怎么实现的 先说结论&#xff1a; chatGPT的流式回复用的就是HTTP请求方案中的server-send-event流式接口&#xff0c;也就是服务端向客户端推流数据。 那eventStream流式接口怎么实现呢&#xff0c;下面就进入正题&#xff01; 文章目录 chatGPT流式回复…

【NVIDIA】Jetson AGX Orin内核、设备树更新指南

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

redis-单节点安装

daemonize yes port 6379 bind 0.0.0.0 requirepass 123456 save 3600 1 300 100 60 10000dir /usr/local/redis dbfilename dump.rdb logfile redis.log pidfile redis.pid##save 3600 1 300 100 60 10000 ##3600秒(一小时),至少有一个值的话,会进行存盘 ##300秒(五分钟),至少…

Java版企业工程项目管理系统源码+java版本+项目模块功能清单+spring cloud +spring boot

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…

macOS上下载安装Kibana并连接ES

下载Kibana 执行以下命令进行&#xff0c;版本号根据你所用的ES版本选择&#xff0c;比如我的是7.10.0 curl -O https://artifacts.elastic.co/downloads/kibana/kibana-7.10.0-darwin-x86_64.tar.gz解压安装Kibana tar -zxvf kibana-7.10.0-darwin-x86_64.tar.gz进行config…