09.责任链模式

news2024/11/17 5:38:20

09. 责任链模式

什么是责任链设计模式?

责任链设计模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着处理者对象组成的链进行传递,直到有一个处理者对象能够处理该请求为止。这种模式的目的是解耦请求的发送者和接收者,使得多个对象都有机会处理请求,从而增强了系统的灵活性。

责任链模式通常包含以下几个角色:

  1. 请求者(Client):发起请求的对象。
  2. 抽象处理者(Handler):定义一个处理请求的接口,通常包含一个方法用于处理请求,以及一个指向下一个处理者的引用。
  3. 具体处理者(Concrete Handler):实现抽象处理者接口的具体类,负责处理它所负责的请求,并决定是否将请求传递给链中的下一个处理者。
  4. 链(Chain):包含多个处理者对象,负责将请求沿着链传递。

责任链模式的工作原理如下:

  • 请求者创建一个请求并将其发送给链的起始处理者。

  • 每个处理者对象检查请求是否由自己处理。

    • 如果能够处理,则处理请求并结束责任链。
    • 如果不能处理,则将请求传递给链中的下一个处理者。
  • 这个过程一直持续,直到请求被处理或传递到链的末端。

责任链模式的优点包括:

  • 增强了系统的灵活性和可扩展性,因为可以动态地添加或移除处理者。
  • 降低了对象之间的耦合度,因为发送者和接收者不需要直接交互。
  • 允许多个对象处理同一个请求,增加了处理请求的灵活性。

责任链模式的缺点包括:

  • 请求的传递路径可能难以跟踪,尤其是在链很长或处理者逻辑复杂的情况下。
  • 责任链可能会导致系统性能问题,因为请求需要在多个对象之间传递。

责任链模式在实际应用中非常广泛,例如在GUI编程中处理事件、在网络编程中处理请求、在工作流系统中处理任务等场景。

举个简单的需求:

假如我们有个登录的场景,在登录处理流程中,需要校验参数、填充参数、登录判断、登录日志记录。我们每步都是环环相扣,此时就可以使用责任链模式。

有几个重要角色:

  • **Action:**责任链中的一个执行动作,主要定义具体执行动作,以及是否需要跳过。
  • **ActionChain:**责任链,用于定义添加执行动作方法,以及调度整条链路动作执行。
  • **ActionContext:**执行动作上下文,定义一个上下文对象,用来在链路执行过程中存储和传输数据。

将上面的步骤看做一个一个执行动作,建立对应的action,使用 chain 将多个 action 进行串联,同时我们可以定义一个context 上下文,用来在各个action之间传输数据。

除此之外,我们也可以通过配置中心,来定义哪些步骤需要执行,哪些可以跳过。

类图如下:

83CCDE77-5B01-46FF-882E-26C9DABFC5AB

代码编写:

1、定义顶级接口

(1)定义责任链执行动作上下文抽象类,用于责任链上下文之间数据传输。

@Data
public abstract class ActionContext implements Serializable, Cloneable {
  private static final long serialVersionUID = 1L;
  /**
   * 执行链名称,用于获取配置
   */
  private String actionChainName;

  /**
   * 跳到结果处理
   */
  private boolean isSkipToResult = false;

  public Object clone() throws CloneNotSupportedException {
     return super.clone();
  }
}

(2)定义责任链执行动作基类

public interface IAction<T extends ActionContext> {
  /**
   * 是否需要跳过
   * @param context 上下文
   * @return    true/false
   */
  default boolean isSkippered(T context) throws Exception{
    if (context.isSkipToResult()) {
      return true;
    }

    // 通过配置中心获取是否需要执行
    List<String> config = ConfigServer.getConfig(context.getActionChainName());
   if (config.contains(getName())) {
      return false;
    }
    return true;
	 }
  
   /**
   	* 执行
		* @param context 上下文
    */
  void execute(T context) throws Exception;

  /**
   * 获取执行动作名称,用于和配置中心进行匹配
   * @return
   */
  String getName();

} 

(3)定义Action 执行链接口

public interface IActionChain<T extends ActionContext> {
  /**
   * 添加一个Action
   * @param action 上下文
   * @return    action链
   */
  IActionChain<T> appendAction(Class<? extends IAction<T>> action);
  IActionChain<T> appendActions(List<Class<? extends IAction<T>>> actions);
  IActionChain<T> appendAction(IAction<T> action);

  /**
   * 执行动作
   * @param context 上下文
   */
  void execute(T context) throws Exception;
}

2、实现接口,定义具体的执行

(1)登录上下文

/**
 * 登录上下文
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class LoginActionContext extends ActionContext {
  /**
   * 失败日志
   */
  private String failMsg;

  private String userName;
  private String password;
  private String token;
  private String ip;
  private String device;

  private Boolean isLoginFlag = true;
}

(2)定义责任链通用模版类

public class RouteActionChain<T extends ActionContext> implements IActionChain<T> {
  private List<IAction<T>> actionChain = new ArrayList<IAction<T>>();

  @Override
  public IActionChain<T> appendAction(Class<? extends IAction<T>> action) {
    actionChain.add(getActionInstance(action));
    return this;
  }

  @Override
  public IActionChain<T> appendActions(List<Class<? extends IAction<T>>> actions) {
    if (CollectionUtils.isEmpty(actions)) {
      return this;
    }
    for (Class<? extends IAction<T>> clazz : actions) {
      actionChain.add(getActionInstance(clazz));
    }
    return this;
  }

  @Override
  public IActionChain<T> appendAction(IAction<T> action) {
    actionChain.add(action);
    return this;
  }

  @Override
  public void execute(T context) throws Exception {
    for (IAction<T> action : actionChain) {
      //如果跳过 就不需要继续执行,这里顺序不能改变
      if (action.isSkippered(context)) {
        continue;
      }
      action.execute(context);
    }
  }

  public static <T extends ActionContext> IAction<T> getActionInstance(Class<? extends IAction<T>> clazz) {
    Collection<? extends IAction<T>> s = BeanUtil.getBeans(clazz);
    if (s != null && s.size() == 1) {
      return s.iterator().next();
    } else {
      throw new RuntimeException("action is not found");
    }
  }
}

(3)定义执行动作

  • 校验参数执行动作
  @Slf4j
  @Component
  public class CheckParamAction implements IAction<LoginActionContext> {
    @Override
    public void execute(LoginActionContext context) {
      // do something
      log.info("CheckParamAction execute......");
      // 使用断言,判断用户名不为空
      try {
        Assert.isTrue(!StringUtils.isEmpty(context.getUserName()), "用户名不能为空");
        Assert.isTrue(!StringUtils.isEmpty(context.getPassword()), "密码不能为空");
      } catch (Exception e) {
        context.setIsLoginFlag(false);
        context.setSkipToResult(true);
        context.setFailMsg(e.getMessage());
      }
    }
    
    @Override
    public String getName() {
      return "CheckParamAction";
    }
  }

填充参数执行动作

  @Slf4j
  @Component
  public class FullParamAction implements IAction<LoginActionContext> {
    @Autowired
    private ConfigServer configServer;
    @Override
    public void execute(LoginActionContext context) throws Exception {
      log.info("FullParamAction execute......");
      // 使用断言,判断用户名不为空
      try {
        // do something
        context.setIp("127.0.0.1");
        context.setDevice("PC");
        context.setToken("123456");
      } catch (Exception e) {
        context.setIsLoginFlag(false);
        context.setSkipToResult(true);
        context.setFailMsg(e.getMessage());
      }
    }
  
    @Override
    public String getName() {
      return "FullParamAction";
    }
  }
  • 登录判断执行动作
 @Slf4j
 @Component
 public class LoginAction implements IAction<LoginActionContext> {
   @Override
   public void execute(LoginActionContext context) throws Exception {
     // 模拟登录
     log.info("LoginAction execute......");
     try {
       // do something
       if(context.getUserName().equals(context.getPassword())) {
         context.setIsLoginFlag(true);
       } else {
         context.setIsLoginFlag(false);
         context.setFailMsg("用户名或密码输入错误");
       }
     } catch (Exception e) {
       context.setIsLoginFlag(false);
       context.setSkipToResult(true);
       context.setFailMsg(e.getMessage());
     }
   }
 
   @Override
   public String getName() {
     return "LoginAction";
   }
 }
  • 登录日志记录执行动作
@Slf4j
@Component
public class LogAction implements IAction<LoginActionContext> {
  @Override
  public void execute(LoginActionContext context) throws Exception {
    log.info("FullParamAction execute......");
    // 使用断言,判断用户名不为空
    try {
      // do something
      log.info("数据库插入登录日志:{}", JSONObject.toJSONString(context));
    } catch (Exception e) {
      context.setIsLoginFlag(false);
      context.setSkipToResult(true);
      context.setFailMsg(e.getMessage());
    }
  }

  @Override
  public String getName() {
    return "LogAction";
  }
}

(4)模拟配置中心

配置需要的执行动作,没有配置的自动跳过

@Component
@Data
public class ConfigServer {
  private static Map<String, List<String>> configMap = new HashMap<>();
  
  @PostConstruct
  public void init()
  {
    ArrayList<String> configList = new ArrayList<>();
    configList.add("CheckParamAction");
    configList.add("FullParamAction");
    configList.add("LogAction");
    configList.add("LoginAction");
    configMap.put("login", configList);
  }

  /**
   * 获取配置列表
   * @param actionChainName
   * @return
   */
  public static List<String> getConfig(String actionChainName) {
    return configMap.getOrDefault(actionChainName, new ArrayList<>());
  }
}

3、测试

定义测试接口

@Service
public class LoginServiceImpl implements LoginService {
  @Override
  public boolean login(String userName, String password) {
    // 创建上下文
    LoginActionContext loginActionContext = new LoginActionContext();
    loginActionContext.setActionChainName("login");
    loginActionContext.setUserName(userName);
    loginActionContext.setPassword(password);

    // 构建责任链
    RouteActionChain<LoginActionContext> chain = new RouteActionChain<>();
    chain.appendAction(CheckParamAction.class);
    chain.appendAction(FullParamAction.class);
    chain.appendAction(LoginAction.class);
    chain.appendAction(LogAction.class);
    try {
      chain.execute(loginActionContext);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    return loginActionContext.getIsLoginFlag();
  }
}

(1)测试正常情况,传输正确的 username 和 password。

A4D6064A-3712-400A-855A-2FB75697DCBC_4_5005_c

所有执行动作正常执行。

(2)测试异常情况, 传输错误的 username 和 password。

D94B82C6-1673-48DA-9493-EED41B96234B_4_5005_c

中间 LoginAction 执行失败,自动跳出责任链,后续执行动作未执行。

到此,一个简单的责任链设计模式的 demo 就已完成。

拓展点:

​ • 可以对接配置中心,动态定义不同业务逻辑中需要执行的动作。

​ • 可以将幂等性校验,添加到判断动作是否执行逻辑中。

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

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

相关文章

docker ps显示的参数具体是什么意思

1&#xff0c;运行一个容器 docker run -d ubuntu:15.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"这段命令的作用是使用 docker run 命令运行一个基于 ubuntu:15.10 镜像的 Docker 容器&#xff0c;并在容器中执行一个无限循环的命令。 具体解…

PID控制中的积分到底是什么,为什么它可以将矩形线转换为曲线?simulink搭建PID控制,积分模块1/s

PID控制中的积分到底是什么&#xff0c;为什么它可以将矩形线转换为曲线&#xff0c; 这个问题呢其实道理很简单&#xff0c;用到的是初中的知识 我们做几个测试案例 如下面matlab搭建了积分1/s 那显示如下&#xff08;红色曲线相当于加速度、蓝色曲线相当于速度&#xff09;&a…

从0到1!得物如何打造通用大模型训练和推理平台

1.背景 近期&#xff0c;GPT大模型的发布给自然语言处理&#xff08;NLP&#xff09;领域带来了令人震撼的体验。随着这一事件的发生&#xff0c;一系列开源大模型也迅速崛起。依据一些评估机构的评估&#xff0c;这些开源模型大模型的表现也相当不错。一些大模型的评测情况可…

【MSSQL】因为数据库正在使用,所以无法获得对数据库的独占访问权” 解决方案汇总

文章目录 前言一、事故现场方案一:设置数据库再单用户模式下工作:方案二:利用SQL语句,断开所有用户连接,并回滚所有事务,具体SQL语句如下:方案三:利用SQL语句,杀死正在使用该数据库的所有进程方案四:修改数据库的登录密码或重启数据库服务(不太建议)二、结尾日志备份…

MDS800-16-ASEMI整流模块800A 1600V

编辑&#xff1a;ll MDS800-16-ASEMI整流模块800A 1600V 型号&#xff1a;MDS800-16 品牌&#xff1a;ASEMI 封装&#xff1a;MDS 批号&#xff1a;2024 分类&#xff1a;整流模块 特性&#xff1a;整流模块、整流桥 平均正向整流电流&#xff08;Id&#xff09;&#…

C语言实现Hash Map(2):Map代码实现详解

在上一节C语言实现Hash Map(1)&#xff1a;Map基础知识入门中&#xff0c;我们介绍了Map的基础概念和在C中的用法。但我写这两篇文章的目的是&#xff0c;能够在C语言中实现这样的一个数据结构&#xff0c;毕竟有时我们的项目中可能会用到Map&#xff0c;但是C语言库中并没有提…

香蕉成熟度检测YOLOV8NANO

香蕉成熟度检测YOLOV8NANO&#xff0c;采用YOLOV8NANO训练&#xff0c;得到PT模型&#xff0c;然后转换成ONNX模型&#xff0c;让OEPNCV调用&#xff0c;从而摆脱PYTORCH依赖&#xff0c;支持C。python&#xff0c;安卓开发。能检测六种香蕉类型freshripe freshunripe overripe…

下载CentOS系统或者下载Ubuntu系统去哪下?

因为Centos官网是挂在国外的服务器上&#xff0c;下载镜像时相比于国内的下载速度会慢很多&#xff0c;分享国内的镜像站去阿里巴巴下载Centos镜像。 首先分享两种下载方式&#xff0c;如果只想下载Centos那么就访问方式一的下载地址即可&#xff0c;如果还想下载其他的系统&a…

Xfce4桌面背景和桌面图标消失问题解决@FreeBSD

问题&#xff1a;Xfce4桌面背景和桌面图标消失 以前碰到过好几次桌面背景和桌面图标消失&#xff0c;整个桌面除了上面一条和下面中间的工具条&#xff0c;其它地方全是黑色的问题&#xff0c;但是这次重启之后也没有修复&#xff0c;整个桌面乌黑一片&#xff0c;啥都没有&am…

[书生·浦语大模型实战营]——第二节:轻松玩转书生·浦语大模型趣味 Demo

1. 部署InternLM2-Chat-1.8B 模型进行智能对话 1.1配置环境 创建开发机 Intern Studio 官网网址&#xff1a;https://studio.intern-ai.org.cn/ 进入官网后&#xff0c;选择创建开发机&#xff0c;填写 开发机名称 后&#xff0c;点击 选择镜像 使用 Cuda11.7-conda 镜像&am…

楼房vr安全逃生模拟体验让你在虚拟环境中亲身体验火灾的紧迫与危险

消防VR安全逃生体验系统是深圳VR公司华锐视点引入了前沿的VR虚拟现实、web3d开发和多媒体交互技术&#xff0c;为用户打造了一个逼真的火灾现场应急逃生模拟演练环境。 相比传统的消防逃生模拟演练&#xff0c;消防VR安全逃生体验系统包含知识讲解和模拟实训演练&#xff0c;体…

码蹄集部分题目(2024OJ赛16期;单调栈集训+差分集训)

&#x1f9c0;&#x1f9c0;&#x1f9c0;单调栈集训 &#x1f96a;单调栈 单调递增栈伪代码&#xff1a; stack<int> st; for(遍历数组) {while(栈不为空&&栈顶元素大于当前元素)//单调递减栈就是把后方判断条件变为小于等于即可{栈顶元素出栈;//同时进行其他…

C语言笔记22 •结构体•

C语言结构体 1.结构体类型的声明 struct Stu { char name[ 20 ]; // 名字 int age; // 年龄 char sex[ 5 ]; // 性别 char id[ 20 ]; // 学号 }; 2.结构体变量的创建和初始化 #include <stdio.h>// 定义一个结构体类型 Point struct Point {int x;int y; };i…

【三个数的最大乘积】python

三层循环必然超时&#xff0c;是的 hhh,换种思路&#xff0c;就很巧 class Solution:def maximumProduct(self, nums: List[int]) -> int:nums.sort()mxnums[-1]*nums[-2]*nums[-3]if nums[0]*nums[1]*nums[-1]>mx:mxnums[0]*nums[1]*nums[-1]return mx

装修:尽显个性品味

家&#xff0c;是心灵的港湾&#xff0c;也是生活的舞台。装修&#xff0c;不仅是对空间的改造&#xff0c;更是对生活态度的诠释。无论是温馨的北欧风&#xff0c;还是华丽的欧式古典&#xff0c;或是简约的现代感&#xff0c;我们的专业团队都能为您量身打造。每一个细节&…

分布式数据库HBase入门指南

目录 概述 HBase 的主要特点包括: HBase 的典型应用场景包括: 访问接口 1. Java API: 2. REST API: 3. Thrift API: 4. 其他访问接口: HBase 数据模型 概述 该模型具有以下特点&#xff1a; 1. 面向列: 2. 多维: 3. 稀疏: 数据存储: 数据访问: HBase 的数据模型…

01-02.Vue的常用指令(二)

01-02.Vue的常用指令&#xff08;二&#xff09; 前言v-model&#xff1a;双向数据绑定v-model举例&#xff1a;实现简易计算器Vue中通过属性绑定为元素设置class 类样式引入方式一&#xff1a;数组写法二&#xff1a;在数组中使用三元表达式写法三&#xff1a;在数组中使用 对…

YOLOv10尝鲜测试五分钟极简配置

最近清华大学团队又推出YOLOv10&#xff0c;真是好家伙了。 安装&#xff1a; pip install supervision githttps://github.com/THU-MIG/yolov10.git下载权重&#xff1a;https://github.com/THU-MIG/yolov10/releases/download/v1.0/yolov10n.pt 预测&#xff1a; from ult…

2024年最全的信息安全、数据安全、网络安全标准分享(可下载)

以上是资料简介和目录&#xff0c;如需下载&#xff0c;请前往星球获取&#xff1a;https://t.zsxq.com/Gz1a0

基于SpringBoot+Vue的人事管理系统

引言 目前,人事管理的系统大都是CS架构的大型系统,很少有面向机关,事业单位内部的基于BS架构的微型人事系统,因此.开发一个基于BS架构的人事信息管理系统是非常必要的.但是基于BS架构的人事系统对于安全是一个大的考验点.在人事信息系统中,功能需简单清晰,可操作性强,其次安全…