hello,我们好,我是张张,「架构精进之路」公号作者。

1、背景

职责链形式(又称职责链形式,The Chain of Responsibility Pattern),作为开发规划中常用的代码规划形式之一,属于行为形式中的一种,历来被咱们开发所熟悉。

近期,团队内一位成员在开发时运用了职责链形式,代码堆地十分多,bug 也多,明显没有达到咱们预期的作用。

实际上,针对这项功能,我认为模版方法更合适!为此,隔壁团队也拿出咱们的事例,进行了集体 code review。

学好规划形式,且不要为了操练,强行运用!本篇旨在重新整理一下职责链规划形式,并诉诸文字,温故而知新。

2、什么是职责链形式

职责链形式是一种了解上比较简略的行为规划形式,它答应开发者经过处理者链进行次第发送,每个链节点在收到恳求后,具备两种才能:

  1. 对其进行处理(消费)

  2. 将其传递给链上的下个处理者

当你想要让一个以上的目标有时机能处理某个恳求时,就能够运用职责链形式。经过职责链形式,为某个恳求创建一个目标链,每个目标链依序查看此恳求,并对其进行处理,或许将它传给链中的下一个目标。

多次代码迭代,应用责任链设计模式

职责链到底处理了什么问题?

  1. 前置查看,削减不必要的后置代码逻辑

  2. 发送者(sender)和接纳者(receiver(s))的逻辑解耦,进步代码灵活性,这一点是最重要的

  3. 经过链路次第传递恳求,也使每一个链节点职责明确,符合单一职责准则

  4. 经过改动链内的成员或调集它们的次第,答应你动态地新增或删除,也进步了代码的灵活性

3、运用事例

假定现在有一个闯关游戏,进入下一关的条件是上一关的分数要高于 xx:

  • 游戏一共 3 个关卡

  • 进入第二关需求第一关的游戏得分大于等于 80

  • 进入第三关需求第二关的游戏得分大于等于 90

3.1 开始方案

那么代码能够这样写:

//第一关
public class FirstPassHandler {
    public int handler(){
        System.out.println("第一关-->FirstPassHandler");
        return 80;
    }
}
//第二关
public class SecondPassHandler {
    public int handler(){
        System.out.println("第二关-->SecondPassHandler");
        return 90;
    }
}
//第三关
public class ThirdPassHandler {
    public int handler(){
        System.out.println("第三关-->ThirdPassHandler,这是最终一关啦");
        return 95;
    }
}
//客户端
public class HandlerClient {
    public static void main(String[] args) {
        FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
        SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
        ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
        int firstScore = firstPassHandler.handler();
        //第一关的分数大于等于80则进入第二关
        if(firstScore >= 80){
            int secondScore = secondPassHandler.handler();
            //第二关的分数大于等于90则进入第二关
            if(secondScore >= 90){
                thirdPassHandler.handler();
            }
        }
    }
}

那么假如这个游戏有 100 关,咱们的代码很可能就会写成这个姿态:

if(第1关经过){
  // 第2关 游戏
  if(第2关经过){
    // 第3关 游戏
    if(第3关经过){
      // 第4关 游戏
      if(第4关经过){
        // 第5关 游戏
        if(第5关经过){
          // 第6关 游戏
          if(第6关经过){
            //...
          }
        }
      }
    }
  }
}

这种代码不仅冗余,而且当咱们要将某两关进行调整时会对代码十分大的改动,这种操作的风险是很高的,因而,该写法十分糟糕。

3.2 初步改造

怎样处理这个问题,咱们能够经过链表将每一关衔接起来,构成职责链的方法,第一关经往后是第二关,第二关经往后是第三关,这样客户端就不需求进行多重 if 的判别了。

public class FirstPassHandler {
    /**
     * 第一关的下一关是 第二关
     */
    private SecondPassHandler secondPassHandler;
    public void setSecondPassHandler(SecondPassHandler secondPassHandler) {
      this.secondPassHandler = secondPassHandler;
    }
    //本关卡游戏得分
    private int play(){
        return 80;
    }
    public int handler(){
        System.out.println("第一关-->FirstPassHandler");
        if(play() >= 80){
            //分数>=80 而且存在下一关才进入下一关
            if(this.secondPassHandler != null){
                return this.secondPassHandler.handler();
            }
        }
        return 80;
    }
}
public class SecondPassHandler {
    /**
     * 第二关的下一关是 第三关
     */
    private ThirdPassHandler thirdPassHandler;
    public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {
        this.thirdPassHandler = thirdPassHandler;
    }
    //本关卡游戏得分
    private int play(){
        return 90;
    }
    public int handler(){
        System.out.println("第二关-->SecondPassHandler");
        if(play() >= 90){
            //分数>=90 而且存在下一关才进入下一关
            if(this.thirdPassHandler != null){
                return this.thirdPassHandler.handler();
            }
        }
        return 90;
    }
}
public class ThirdPassHandler {
    //本关卡游戏得分
    private int play(){
        return 95;
    }
    /**
     * 这是最终一关,因而没有下一关
     */
    public int handler(){
        System.out.println("第三关-->ThirdPassHandler,这是最终一关啦");
        return play();
    }
}
public class HandlerClient {
    public static void main(String[] args) {
        FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
        SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
        ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
        firstPassHandler.setSecondPassHandler(secondPassHandler);//第一关的下一关是第二关
        secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二关的下一关是第三关
        //说明:由于第三关是最终一关,因而没有下一关
        //开始调用第一关 每一个关卡是否进入下一关卡 在每个关卡中判别
        firstPassHandler.handler();
    }
}

初步改造方案的缺陷:

  • 每个关卡中都有下一关的成员变量而且是不相同的,构成链很不便利;

  • 代码的扩展性十分不好。

3.3 职责链优化

已然每个关卡中都有下一关的成员变量而且是不相同的,那么咱们能够在关卡上笼统出一个父类或许接口,然后每个详细的关卡去承继或许实现。

有了思路,咱们先来简略介绍一下职责链规划形式的基本组成:

  • 笼统处理者(Handler)人物:界说一个处理恳求的接口,包含笼统处理方法和一个后继衔接。

  • 详细处理者(Concrete Handler)人物:实现笼统处理者的处理方法,判别能否处理本次恳求,假如能够处理恳求则处理,不然将该恳求转给它的后继者。

  • 客户类(Client)人物:创建处理链,并向链头的详细处理者目标提交恳求,它不关心处理细节和恳求的传递过程。

多次代码迭代,应用责任链设计模式

public abstract class AbstractHandler {
    /**
     * 下一关用当时笼统类来接纳
     */
    protected AbstractHandler next;
    public void setNext(AbstractHandler next) {
        this.next = next;
    }
    public abstract int handler();
}
public class FirstPassHandler extends AbstractHandler{
    private int play(){
        return 80;
    }
    @Override
    public int handler(){
        System.out.println("第一关-->FirstPassHandler");
        int score = play();
        if(score >= 80){
            //分数>=80 而且存在下一关才进入下一关
            if(this.next != null){
                return this.next.handler();
            }
        }
        return score;
    }
}
public class SecondPassHandler extends AbstractHandler{
    private int play(){
        return 90;
    }
    public int handler(){
        System.out.println("第二关-->SecondPassHandler");
        int score = play();
        if(score >= 90){
            //分数>=90 而且存在下一关才进入下一关
            if(this.next != null){
                return this.next.handler();
            }
        }
        return score;
    }
}
public class ThirdPassHandler extends AbstractHandler{
    private int play(){
        return 95;
    }
    public int handler(){
        System.out.println("第三关-->ThirdPassHandler");
        int score = play();
        if(score >= 95){
            //分数>=95 而且存在下一关才进入下一关
            if(this.next != null){
                return this.next.handler();
            }
        }
        return score;
    }
}
public class HandlerClient {
    public static void main(String[] args) {
        FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
        SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
        ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
        // 和上面没有更改的客户端代码相比,只有这儿的set方法发生变化,其他都是相同的
        firstPassHandler.setNext(secondPassHandler);//第一关的下一关是第二关
        secondPassHandler.setNext(thirdPassHandler);//第二关的下一关是第三关
        //由于第三关是最终一关,因而没有下一关
        //从第一个关卡开始
        firstPassHandler.handler();
    }
}

3.4 职责链工厂优化

对于上面的恳求链,咱们也能够把这个联系保护到装备文件中或许一个枚举中。我将运用枚举来教会我们怎样动态的装备恳求链而且将每个恳求者构成一条调用链。

public enum GatewayEnum {
    // handlerId, 阻拦者名称,全限制类名,preHandlerId,nextHandlerId
    API_HANDLER(new GatewayEntity(1, "api接口限流", "cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler", null, 2));
    BLACKLIST_HANDLER(new GatewayEntity(2, "黑名单阻拦", "cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler", 1, 3));
    SESSION_HANDLER(new GatewayEntity(3, "用户会话阻拦", "cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler", 2, null));
    GatewayEntity gatewayEntity;
    public GatewayEntity getGatewayEntity() {
        return gatewayEntity;
    }
    GatewayEnum(GatewayEntity gatewayEntity) {
        this.gatewayEntity = gatewayEntity;
    }
}
public class GatewayEntity {
    private String name;
    private String conference;
    private Integer handlerId;
    private Integer preHandlerId;
    private Integer nextHandlerId;
}
public interface GatewayDao {
    /**
     * 根据 handlerId 获取装备项
     * @param handlerId
     * @return
     */
    GatewayEntity getGatewayEntity(Integer handlerId);
    /**
     * 获取第一个处理者
     * @return
     */
    GatewayEntity getFirstGatewayEntity();
}
public class GatewayImpl implements GatewayDao {
    /**
     * 初始化,将枚举中装备的handler初始化到map中,便利获取
     */
    private static Map<Integer, GatewayEntity> gatewayEntityMap = new HashMap<>();
    static {
        GatewayEnum[] values = GatewayEnum.values();
        for (GatewayEnum value : values) {
            GatewayEntity gatewayEntity = value.getGatewayEntity();
            gatewayEntityMap.put(gatewayEntity.getHandlerId(), gatewayEntity);
        }
    }
    @Override
    public GatewayEntity getGatewayEntity(Integer handlerId) {
        return gatewayEntityMap.get(handlerId);
    }
    @Override
    public GatewayEntity getFirstGatewayEntity() {
        for (Map.Entry<Integer, GatewayEntity> entry : gatewayEntityMap.entrySet()) {
            GatewayEntity value = entry.getValue();
            //  没有上一个handler的就是第一个
            if (value.getPreHandlerId() == null) {
                return value;
            }
        }
        return null;
    }
}
public class GatewayHandlerEnumFactory {
    private static GatewayDao gatewayDao = new GatewayImpl();
    // 提供静态方法,获取第一个handler
    public static GatewayHandler getFirstGatewayHandler() {
        GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();
        GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);
        if (firstGatewayHandler == null) {
            return null;
        }
        GatewayEntity tempGatewayEntity = firstGatewayEntity;
        Integer nextHandlerId = null;
        GatewayHandler tempGatewayHandler = firstGatewayHandler;
        // 迭代遍历所有handler,以及将它们链接起来
        while ((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null) {
            GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);
            GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);
            tempGatewayHandler.setNext(gatewayHandler);
            tempGatewayHandler = gatewayHandler;
            tempGatewayEntity = gatewayEntity;
        }
        // 返回第一个handler
        return firstGatewayHandler;
    }
    /**
     * 反射实体化详细的处理者
     * @param firstGatewayEntity
     * @return
     */
    private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {
        // 获取全限制类名
        String className = firstGatewayEntity.getConference(); 
        try {
            // 根据全限制类名,加载并初始化该类,即会初始化该类的静态段
            Class<?> clazz = Class.forName(className);
            return (GatewayHandler) clazz.newInstance();
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }
}
public class GetewayClient {
    public static void main(String[] args) {
        GetewayHandler firstGetewayHandler = GetewayHandlerEnumFactory.getFirstGetewayHandler();
        firstGetewayHandler.service();
    }
}

4、结语

最终借用《Head First Design Patterns》一书中对规划形式怎样运用的表述,做一个收尾,深认为然。

  1. 为实际需求的扩展运用形式,不要仅仅为了设想的需求而运用形式

  2. 简略才是王道,假如不必形式就能规划出更简略的方案,那就去干吧

  3. 形式是工具而不是规矩,需求被适当地调整以符合实际的需求

参考

  • 《Dive-into Design Patter》 Alexander Shvets

  • 《Head First Design Patterns》 Elisabeth Freeman and Kathy Sierra

– END –

期望今天的解说对我们有所帮助,谢谢!

Thanks for reading!

作者:架构精进之路,十年研发风雨路,大厂架构师,CSDN 博客专家,专注架构技术沉积学习及共享,工作与认知晋级,坚持共享接地气儿的干货文章,期待与你一起成长。
重视并私信我回复“01”,送你一份程序员成长进阶大礼包,欢迎勾搭。