09.责任链模式

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 就已完成。

拓展点:

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

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

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值