• 一文弄懂责任链设计模式


    Reference

    [1] zhuanlan.zhihu.com/p/99334096, 本文主要借鉴该篇文章,如有侵权,联系删除

    [2] refactoringguru.cn/design-patt…

    [3] c.biancheng.net/view/1383.h…

    什么是责任链

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

    image-20211211232146370

    场景

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

    • 多条件流程判断:权限控制
    • ERP 系统流程审批:总经理、人事经理、项目经理
    • Java 过滤器 的底层实现 Filter

    如果不使用该设计模式,那么当需求有所改变时,就会使得代码臃肿或者难以维护,例如下面的例子

    反例

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

    1. 游戏一共 3 个关卡
    2. 进入第二关需要第一关的游戏得分大于等于 80
    3. 进入第三关需要第二关的游戏得分大于等于 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. }
    39. 复制代码

    那么如果这个游戏有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. }
    19. 复制代码

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

    初步改造

    如何解决这个问题,我们可以通过链表将每一关连接起来,形成责任链的方式,第一关通过后是第二关,第二关通过后是第三关 ....,这样客户端就不需要进行多重 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. }
    72. 复制代码

    缺点

    现有模式的缺点

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

    责任链改造

    • 既然每个关卡中都有下一关的成员变量并且是不一样的,那么我们可以在关卡上抽象出一个父类或者接口,然后每个具体的关卡去继承或者实现

    有了思路,我们先来简单介绍一下责任链设计模式的基本组成

    • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
    • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
    • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

    image-20211211233740916

    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. }
    73. 复制代码

    责任链工厂改造

    对于上面的请求链,我们也可以把这个关系维护到配置文件中或者一个枚举中。我将使用枚举来教会大家怎么动态的配置请求链并且将每个请求者形成一条调用链

    image-20211212113926778

    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. }
    110. 复制代码

    聊聊其他

    很久没写文章,本文主要还是借鉴了 Reference [1] 该作者的图文,说实话,写的特别好,尤其是其中的责任链工厂,我觉得很有意思。

    设计模式确实是一门艺术,仍需努力呀!




     

    来源:https://blog.csdn.net/q1472750149/article/details/121886327
  • 相关阅读:
    金融法规
    大型网站架构演化
    用一张决策图来重新思考你的网站
    互联网产品的一般商业模式
    怎样开始冥想
    创造安宁舒适工作日的17个办法
    人生规划和GTD——"知"、"得"与"合"
    立即行动的七个方法
    文件归档的学问
    入门GTD时间管理系统必读
  • 原文地址:https://www.cnblogs.com/konglxblog/p/16706278.html
Copyright © 2020-2023  润新知