• JAVA设计模式-责任链模式


    一.概念

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

             在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。

    二.角色

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

    三.优缺点

    优点
    • 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
    • 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
    • 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
    • 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
    • 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
    缺点
    • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
    • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
    • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
    • 可能不容易观察运行时的特征,有碍于除错。

    四.使用场景

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

    • 多条件流程判断:权限控制

    • ERP 系统流程审批:总经理、人事经理、项目经理

    • Java 过滤器 的底层实现 Filter

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

    五.实现

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

    1. 游戏一共 3 个关卡

    2. 进入第二关需要第一关的游戏得分大于等于 80

    3. 进入第三关需要第二关的游戏得分大于等于 90

    1.面向过程代码编写
    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. }

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

    2.责任链改造

    1.编写AbstractHandler 抽象类,用于责任链中的对象传递

    2.编写关卡1,2,3 ,继承抽象类

    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. }

    3.责任链工厂改造

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

    1. public enum GatewayEnum {
    2. // handlerId, 拦截者名称,全限定类名,preHandlerId,nextHandlerId
    3. API_HANDLER(new GatewayEntity(1, "api接口限流",
    4. "cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler", null, 2)),
    5. BLACKLIST_HANDLER(new GatewayEntity(2, "黑名单拦截",
    6. "cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler", 1, 3)),
    7. SESSION_HANDLER(new GatewayEntity(3, "用户会话拦截",
    8. "cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler", 2, null));
    9. GatewayEntity gatewayEntity;
    10. public GatewayEntity getGatewayEntity() {
    11. return gatewayEntity;
    12. }
    13. GatewayEnum(GatewayEntity gatewayEntity) {
    14. this.gatewayEntity = gatewayEntity;
    15. }
    16. }
    17. public class GatewayEntity {
    18. private String name;
    19. private String conference;
    20. private Integer handlerId;
    21. private Integer preHandlerId;
    22. private Integer nextHandlerId;
    23. }
    24. public interface GatewayDao {
    25. /**
    26. * 根据 handlerId 获取配置项
    27. * @param handlerId
    28. * @return
    29. */
    30. GatewayEntity getGatewayEntity(Integer handlerId);
    31. /**
    32. * 获取第一个处理者
    33. * @return
    34. */
    35. GatewayEntity getFirstGatewayEntity();
    36. }
    37. public class GatewayImpl implements GatewayDao {
    38. /**
    39. * 初始化,将枚举中配置的handler初始化到map中,方便获取
    40. */
    41. private static Map gatewayEntityMap = new HashMap<>();
    42. static {
    43. GatewayEnum[] values = GatewayEnum.values();
    44. for (GatewayEnum value : values) {
    45. GatewayEntity gatewayEntity = value.getGatewayEntity();
    46. gatewayEntityMap.put(gatewayEntity.getHandlerId(), gatewayEntity);
    47. }
    48. }
    49. @Override
    50. public GatewayEntity getGatewayEntity(Integer handlerId) {
    51. return gatewayEntityMap.get(handlerId);
    52. }
    53. @Override
    54. public GatewayEntity getFirstGatewayEntity() {
    55. for (Map.Entry entry : gatewayEntityMap.entrySet()) {
    56. GatewayEntity value = entry.getValue();
    57. // 没有上一个handler的就是第一个
    58. if (value.getPreHandlerId() == null) {
    59. return value;
    60. }
    61. }
    62. return null;
    63. }
    64. }
    65. public class GatewayHandlerEnumFactory {
    66. private static GatewayDao gatewayDao = new GatewayImpl();
    67. // 提供静态方法,获取第一个handler
    68. public static GatewayHandler getFirstGatewayHandler() {
    69. GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();
    70. GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);
    71. if (firstGatewayHandler == null) {
    72. return null;
    73. }
    74. GatewayEntity tempGatewayEntity = firstGatewayEntity;
    75. Integer nextHandlerId = null;
    76. GatewayHandler tempGatewayHandler = firstGatewayHandler;
    77. // 迭代遍历所有handler,以及将它们链接起来
    78. while ((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null) {
    79. GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);
    80. GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);
    81. tempGatewayHandler.setNext(gatewayHandler);
    82. tempGatewayHandler = gatewayHandler;
    83. tempGatewayEntity = gatewayEntity;
    84. }
    85. // 返回第一个handler
    86. return firstGatewayHandler;
    87. }
    88. /**
    89. * 反射实体化具体的处理者
    90. * @param firstGatewayEntity
    91. * @return
    92. */
    93. private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {
    94. // 获取全限定类名
    95. String className = firstGatewayEntity.getConference();
    96. try {
    97. // 根据全限定类名,加载并初始化该类,即会初始化该类的静态段
    98. Class clazz = Class.forName(className);
    99. return (GatewayHandler) clazz.newInstance();
    100. } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
    101. e.printStackTrace();
    102. }
    103. return null;
    104. }
    105. }
    106. public class GetewayClient {
    107. public static void main(String[] args) {
    108. GetewayHandler firstGetewayHandler = GetewayHandlerEnumFactory.getFirstGetewayHandler();
    109. firstGetewayHandler.handler();
    110. }
    111. }

    六.责任链模式的纯与非纯模式

    职责链模式可分为纯的职责链模式和不纯的职责链模式两种

    • 纯的职责链模式

    ​ 一个纯的职责链模式要求一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。而且在纯的职责链模式中,要求一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况。在前面的采购单审批实例中应用的是纯的职责链模式。

    • 不纯的职责链模式

      ​ 在一个不纯的职责链模式中允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求,而且一个请求可以最终不被任何处理者对象所接收。Java AWT 1.0中的事件处理模型应用的是不纯的职责链模式,其基本原理如下:由于窗口组件(如按钮、文本框等)一般都位于容器组件中,因此当事件发生在某一个组件上时,先通过组件对象的handleEvent()方法将事件传递给相应的事件处理方法,该事件处理方法将处理此事件,然后决定是否将该事件向上一级容器组件传播;上级容器组件在接到事件之后可以继续处理此事件并决定是否继续向上级容器组件传播,如此反复,直到事件到达顶层容器组件为止;如果一直传到最顶层容器仍没有处理方法,则该事件不予处理。每一级组件在接收到事件时,都可以处理此事件,而不论此事件是否在上一级已得到处理,还存在事件未被处理的情况。显然,这就是不纯的职责链模式,早期的Java AWT事件模型(JDK 1.0及更早)中的这种事件处理机制又叫事件浮升(Event Bubbling)机制。从Java.1.1以后,JDK使用观察者模式代替职责链模式来处理事件。目前,在JavaScript中仍然可以使用这种事件浮升机制来进行事件处理。

  • 相关阅读:
    含文档+PPT+源码等]精品基于Uniapp+SSM实现的互联网云数据环境下的供销APP[包运行成功]
    持续集成/持续部署:Git
    ffmpeg下载地址
    2022年最新前端面试题(更新)
    Java毕设项目飞机订票管理系统计算机(附源码+系统+数据库+LW)
    谈谈我对云原生与软件供应链安全的思考
    Spring @Transactional 原理解析
    Flink Hive Catalog操作案例
    目标检测 YOLOv5 - 模型的输出
    二叉树题目:最大二叉树
  • 原文地址:https://blog.csdn.net/qq_45443475/article/details/133784927