同事写了一个责任链模式,bug 无数...

打印 上一主题 下一主题

主题 650|帖子 650|积分 1950

背景

最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。
实际上,针对导入功能,我认为模版方法更合适!为此,隔壁团队也拿出我们的案例,进行了集体 code review。
学好设计模式,且不要为了练习,强行使用!让原本 100 行就能实现的功能,写了 3000 行!对错暂且不论,我们先一起看看责任链设计模式吧!
什么是责任链

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

使用场景

责任链的使用场景还是比较多的:

  • 多条件流程判断:权限控制
  • ERP 系统流程审批:总经理、人事经理、项目经理
  • Java 过滤器的底层实现 Filter
如果不使用该设计模式,那么当需求有所改变时,就会使得代码臃肿或者难以维护,例如下面的例子。
| 反例

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

  • 游戏一共 3 个关卡
  • 进入第二关需要第一关的游戏得分大于等于 80
  • 进入第三关需要第二关的游戏得分大于等于 90
那么代码可以这样写:
  1. //第一关
  2. public class FirstPassHandler {
  3.     public int handler(){
  4.         System.out.println("第一关-->FirstPassHandler");
  5.         return 80;
  6.     }
  7. }
  8. //第二关
  9. public class SecondPassHandler {
  10.     public int handler(){
  11.         System.out.println("第二关-->SecondPassHandler");
  12.         return 90;
  13.     }
  14. }
  15. //第三关
  16. public class ThirdPassHandler {
  17.     public int handler(){
  18.         System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");
  19.         return 95;
  20.     }
  21. }
  22. //客户端
  23. public class HandlerClient {
  24.     public static void main(String[] args) {
  25.         FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
  26.         SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
  27.         ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
  28.         int firstScore = firstPassHandler.handler();
  29.         //第一关的分数大于等于80则进入第二关
  30.         if(firstScore >= 80){
  31.             int secondScore = secondPassHandler.handler();
  32.             //第二关的分数大于等于90则进入第二关
  33.             if(secondScore >= 90){
  34.                 thirdPassHandler.handler();
  35.             }
  36.         }
  37.     }
  38. }
复制代码
那么如果这个游戏有 100 关,我们的代码很可能就会写成这个样子:
  1. if(第1关通过){
  2.     // 第2关 游戏
  3.     if(第2关通过){
  4.         // 第3关 游戏
  5.         if(第3关通过){
  6.            // 第4关 游戏
  7.             if(第4关通过){
  8.                 // 第5关 游戏
  9.                 if(第5关通过){
  10.                     // 第6关 游戏
  11.                     if(第6关通过){
  12.                         //...
  13.                     }
  14.                 }
  15.             }
  16.         }
  17.     }
  18. }
复制代码
这种代码不仅冗余,并且当我们要将某两关进行调整时会对代码非常大的改动,这种操作的风险是很高的,因此,该写法非常糟糕。
更多设计模式系列教程:https://www.javastack.cn/
| 初步改造

如何解决这个问题,我们可以通过链表将每一关连接起来,形成责任链的方式,第一关通过后是第二关,第二关通过后是第三关....
这样客户端就不需要进行多重 if 的判断了:
  1. public class FirstPassHandler {
  2.     /**
  3.      * 第一关的下一关是 第二关
  4.      */
  5.     private SecondPassHandler secondPassHandler;
  6.     public void setSecondPassHandler(SecondPassHandler secondPassHandler) {
  7.         this.secondPassHandler = secondPassHandler;
  8.     }
  9.     //本关卡游戏得分
  10.     private int play(){
  11.         return 80;
  12.     }
  13.     public int handler(){
  14.         System.out.println("第一关-->FirstPassHandler");
  15.         if(play() >= 80){
  16.             //分数>=80 并且存在下一关才进入下一关
  17.             if(this.secondPassHandler != null){
  18.                 return this.secondPassHandler.handler();
  19.             }
  20.         }
  21.         return 80;
  22.     }
  23. }
  24. public class SecondPassHandler {
  25.     /**
  26.      * 第二关的下一关是 第三关
  27.      */
  28.     private ThirdPassHandler thirdPassHandler;
  29.     public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {
  30.         this.thirdPassHandler = thirdPassHandler;
  31.     }
  32.     //本关卡游戏得分
  33.     private int play(){
  34.         return 90;
  35.     }
  36.     public int handler(){
  37.         System.out.println("第二关-->SecondPassHandler");
  38.         if(play() >= 90){
  39.             //分数>=90 并且存在下一关才进入下一关
  40.             if(this.thirdPassHandler != null){
  41.                 return this.thirdPassHandler.handler();
  42.             }
  43.         }
  44.         return 90;
  45.     }
  46. }
  47. public class ThirdPassHandler {
  48.     //本关卡游戏得分
  49.     private int play(){
  50.         return 95;
  51.     }
  52.     /**
  53.      * 这是最后一关,因此没有下一关
  54.      */
  55.     public int handler(){
  56.         System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");
  57.         return play();
  58.     }
  59. }
  60. public class HandlerClient {
  61.     public static void main(String[] args) {
  62.         FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
  63.         SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
  64.         ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
  65.         firstPassHandler.setSecondPassHandler(secondPassHandler);//第一关的下一关是第二关
  66.         secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二关的下一关是第三关
  67.         //说明:因为第三关是最后一关,因此没有下一关
  68.         //开始调用第一关 每一个关卡是否进入下一关卡 在每个关卡中判断
  69.         firstPassHandler.handler();
  70.     }
  71. }
复制代码
| 缺点

现有模式的缺点:

  • 每个关卡中都有下一关的成员变量并且是不一样的,形成链很不方便
  • 代码的扩展性非常不好


| 责任链改造

既然每个关卡中都有下一关的成员变量并且是不一样的,那么我们可以在关卡上抽象出一个父类或者接口,然后每个具体的关卡去继承或者实现。
有了思路,我们先来简单介绍一下责任链设计模式的基本组成:

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
  1. public abstract class AbstractHandler {
  2.     /**
  3.      * 下一关用当前抽象类来接收
  4.      */
  5.     protected AbstractHandler next;
  6.     public void setNext(AbstractHandler next) {
  7.         this.next = next;
  8.     }
  9.     public abstract int handler();
  10. }
  11. public class FirstPassHandler extends AbstractHandler{
  12.     private int play(){
  13.         return 80;
  14.     }
  15.     @Override
  16.     public int handler(){
  17.         System.out.println("第一关-->FirstPassHandler");
  18.         int score = play();
  19.         if(score >= 80){
  20.             //分数>=80 并且存在下一关才进入下一关
  21.             if(this.next != null){
  22.                 return this.next.handler();
  23.             }
  24.         }
  25.         return score;
  26.     }
  27. }
  28. public class SecondPassHandler extends AbstractHandler{
  29.     private int play(){
  30.         return 90;
  31.     }
  32.     public int handler(){
  33.         System.out.println("第二关-->SecondPassHandler");
  34.         int score = play();
  35.         if(score >= 90){
  36.             //分数>=90 并且存在下一关才进入下一关
  37.             if(this.next != null){
  38.                 return this.next.handler();
  39.             }
  40.         }
  41.         return score;
  42.     }
  43. }
  44. public class ThirdPassHandler extends AbstractHandler{
  45.     private int play(){
  46.         return 95;
  47.     }
  48.     public int handler(){
  49.         System.out.println("第三关-->ThirdPassHandler");
  50.         int score = play();
  51.         if(score >= 95){
  52.             //分数>=95 并且存在下一关才进入下一关
  53.             if(this.next != null){
  54.                 return this.next.handler();
  55.             }
  56.         }
  57.         return score;
  58.     }
  59. }
  60. public class HandlerClient {
  61.     public static void main(String[] args) {
  62.         FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关
  63.         SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关
  64.         ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关
  65.         // 和上面没有更改的客户端代码相比,只有这里的set方法发生变化,其他都是一样的
  66.         firstPassHandler.setNext(secondPassHandler);//第一关的下一关是第二关
  67.         secondPassHandler.setNext(thirdPassHandler);//第二关的下一关是第三关
  68.         //说明:因为第三关是最后一关,因此没有下一关
  69.         //从第一个关卡开始
  70.         firstPassHandler.handler();
  71.     }
  72. }
复制代码
| 责任链工厂改造

对于上面的请求链,我们也可以把这个关系维护到配置文件中或者一个枚举中。我将使用枚举来教会大家怎么动态的配置请求链并且将每个请求者形成一条调用链。
  1. public enum GatewayEnum {
  2.     // handlerId, 拦截者名称,全限定类名,preHandlerId,nextHandlerId
  3.     API_HANDLER(new GatewayEntity(1, "api接口限流", "cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler", null, 2)),
  4.     BLACKLIST_HANDLER(new GatewayEntity(2, "黑名单拦截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler", 1, 3)),
  5.     SESSION_HANDLER(new GatewayEntity(3, "用户会话拦截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler", 2, null)),
  6.     ;
  7.     GatewayEntity gatewayEntity;
  8.     public GatewayEntity getGatewayEntity() {
  9.         return gatewayEntity;
  10.     }
  11.     GatewayEnum(GatewayEntity gatewayEntity) {
  12.         this.gatewayEntity = gatewayEntity;
  13.     }
  14. }
  15. public class GatewayEntity {
  16.     private String name;
  17.     private String conference;
  18.     private Integer handlerId;
  19.     private Integer preHandlerId;
  20.     private Integer nextHandlerId;
  21. }
  22. public interface GatewayDao {
  23.     /**
  24.      * 根据 handlerId 获取配置项
  25.      * @param handlerId
  26.      * @return
  27.      */
  28.     GatewayEntity getGatewayEntity(Integer handlerId);
  29.     /**
  30.      * 获取第一个处理者
  31.      * @return
  32.      */
  33.     GatewayEntity getFirstGatewayEntity();
  34. }
  35. public class GatewayImpl implements GatewayDao {
  36.     /**
  37.      * 初始化,将枚举中配置的handler初始化到map中,方便获取
  38.      */
  39.     private static Map<Integer, GatewayEntity> gatewayEntityMap = new HashMap<>();
  40.     static {
  41.         GatewayEnum[] values = GatewayEnum.values();
  42.         for (GatewayEnum value : values) {
  43.             GatewayEntity gatewayEntity = value.getGatewayEntity();
  44.             gatewayEntityMap.put(gatewayEntity.getHandlerId(), gatewayEntity);
  45.         }
  46.     }
  47.     @Override
  48.     public GatewayEntity getGatewayEntity(Integer handlerId) {
  49.         return gatewayEntityMap.get(handlerId);
  50.     }
  51.     @Override
  52.     public GatewayEntity getFirstGatewayEntity() {
  53.         for (Map.Entry<Integer, GatewayEntity> entry : gatewayEntityMap.entrySet()) {
  54.             GatewayEntity value = entry.getValue();
  55.             //  没有上一个handler的就是第一个
  56.             if (value.getPreHandlerId() == null) {
  57.                 return value;
  58.             }
  59.         }
  60.         return null;
  61.     }
  62. }
  63. public class GatewayHandlerEnumFactory {
  64.     private static GatewayDao gatewayDao = new GatewayImpl();
  65.     // 提供静态方法,获取第一个handler
  66.     public static GatewayHandler getFirstGatewayHandler() {
  67.         GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();
  68.         GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);
  69.         if (firstGatewayHandler == null) {
  70.             return null;
  71.         }
  72.         GatewayEntity tempGatewayEntity = firstGatewayEntity;
  73.         Integer nextHandlerId = null;
  74.         GatewayHandler tempGatewayHandler = firstGatewayHandler;
  75.         // 迭代遍历所有handler,以及将它们链接起来
  76.         while ((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null) {
  77.             GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);
  78.             GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);
  79.             tempGatewayHandler.setNext(gatewayHandler);
  80.             tempGatewayHandler = gatewayHandler;
  81.             tempGatewayEntity = gatewayEntity;
  82.         }
  83.     // 返回第一个handler
  84.         return firstGatewayHandler;
  85.     }
  86.     /**
  87.      * 反射实体化具体的处理者
  88.      * @param firstGatewayEntity
  89.      * @return
  90.      */
  91.     private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {
  92.         // 获取全限定类名
  93.         String className = firstGatewayEntity.getConference();
  94.         try {
  95.             // 根据全限定类名,加载并初始化该类,即会初始化该类的静态段
  96.             Class<?> clazz = Class.forName(className);
  97.             return (GatewayHandler) clazz.newInstance();
  98.         } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
  99.             e.printStackTrace();
  100.         }
  101.         return null;
  102.     }
  103. }
  104. public class GetewayClient {
  105.     public static void main(String[] args) {
  106.         GetewayHandler firstGetewayHandler = GetewayHandlerEnumFactory.getFirstGetewayHandler();
  107.         firstGetewayHandler.service();
  108.     }
  109. }
复制代码
设计模式有很多,责任链只是其中的一种,我觉得很有意思,非常值得一学。设计模式确实是一门艺术,仍需努力呀!
版权声明:本文为CSDN博主「Java小海.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/q1472750149/article/details/121886327
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表