请假怎么还没批?责任链出问题了?
收藏


码个蛋(codeegg)第 732次推文
作者: 徐公码字
原文: https://mp.weixin.qq.com/s/Q1T3ofslSw08JtGgkzr17w


这几天在重新阅读 Okhttp 源码的时候,看到了 Okhttp Interceptor 的应用,想起了责任链模式,于是,动手将自己对责任链模式的理解记录了下来,希望对大家有所帮助。


啥是责任链模式?


责任链设计模式属于行为设计模式,简单来说,一个请求由链表进行处理(链表上由多个对象组成),具体由那个对象处理,需要根据条件判断。


他的处理逻辑大概是这样的,从链头开始传递,直到找到处理他的对象为止。


责任链设计模式有一个很明显的好处,请求者与处理者直接耦合度大大降低,他们之间甚至可以互相不知道对方的存在。


下面,我们先来看一下责任链模式的 UML 图。



可以看到,主要有几个角色


抽象处理者( Handler )角色:定义一个处理请求的抽象类。

ConcreteHandler 具体事件处理者,一般来说,他持有下一个处理者的引用,当他不处理目前事件的时候,会传递给下一个处理者处理,即 successor 处理。

Client 方,即发起方,当我们发起请求的时候,直接交给 Handler 链表去处理即可


a. 例子


我们来模拟一个公司的请假流程,比如说,在公司中,我们平时请假,在 OA 上面发起申请流程,少于 3 天的一般由组长审批即可,大于 3 天的小于 7 天的由部门总经理审批,大于七天的由公司总经理审批。


这个时候,我们可以考虑用责任链模式来设计。



接下来,我们一起来看一下,我们设计的接口


package com.xj.chain;

public interface Handler {

  /**
   * 处理请求
   * @param dayNums
   */

  void handleRequest(int dayNums);

  /**
   * 设置下一个执行者
   */

  void setSuccesor(Handler handler);

  /**
   * 获取下一个执行者
   * @return
   */

  Handler getNextSucccesor();

}


可以看到,主要有三个方法 handleRequest(int dayNums); 主要用来处理请求,void setSuccesor(Handler handler) 设置下一个执行者,从而形成链表。Handler getNextSucccesor() 获取下一个执行者,通常的做法是,如果当前处理者不处理该请求,交给下一个处理者。


Leader 类


public class Leader implements Handler {

  public String mName;
  public Handler mNextHandler;
  public int mCouldHandlerNum;

  public Leader(String name, int couldHandlerNum) {
    mName = name;
    mCouldHandlerNum = couldHandlerNum;
  }

  @Override
  public void setSuccesor(Handler handler) {
    mNextHandler = handler;
  }

  @Override
  public Handler getNextSucccesor() {
    return mNextHandler;
  }

  @Override
  public void handleRequest(int dayNums) {
    if (dayNums <= mCouldHandlerNum) {
      System.out.println(mName + " 同意了你的申请, dayNums = " + dayNums);
    } else {
      Handler nextSucccesor = getNextSucccesor();
      if (nextSucccesor != null) {
        nextSucccesor.handleRequest(dayNums);
      } else {
        System.out.println(mName + " 拒绝了你的申请 dayNums = " + dayNums);
      }
    }
  }
}


leader 类,实现了 Handler 接口,主要看 handlerRequest 方法,他封装了一些基本的逻辑

- 当请求天数少于 mCouldHandlerNum 即能够处理的最大天数,直接交给当前处理者处理

- 否则,交给下一个处理者处理。


Leader 的子类

public class TeamLeader extends Leader {
  public TeamLeader(String name, int couldHandlerNum) {
    super(name, couldHandlerNum);
  }
}

public class Departmentdirector extends Leader{
  public Departmentdirector(String name, int couldHandlerNum) {
    super(name, couldHandlerNum);
  }
}


public class GeneralManager extends Leader{
  public GeneralManager(String name, int couldHandlerNum) {
    super(name, couldHandlerNum);
  }
}


可以看到, 这里我们 leader 的子类只重写了构造方法,并没有重写其他方法。这是因为 handleRequest 的基本逻辑我们已经基类当中,子类不需要重写。因此,这里的 DirectLeader,Departmentdirector,GeneralManager 实际上是可有可无的。


然而,在实际开发当中,部分总经理,总经理,他们的职责肯定有很多不同,所以这里分别用不同的子类实现。


测试代码


package com.xj.chain;

public class ClientTest {

  public static void main(String[] args) {
    Leader directLeader = new TeamLeader("TeamLeader", 3);
    Leader departmentdirector = new Departmentdirector("Departmentdirector", 7);
    Leader generalManager = new GeneralManager("GeneralManager", Integer.MAX_VALUE);
    
    directLeader.setSuccesor(departmentdirector);
    departmentdirector.setSuccesor(generalManager);
    
    // 请假三天
    directLeader.handleRequest(3);
    // 请假六天
    directLeader.handleRequest(6);
    // 请假100天
    directLeader.handleRequest(100);

  }

}


运行以上代码,可以看到以下输出


DirectLeader 同意了你的申请, dayNums = 3
Departmentdirector 同意了你的申请, dayNums = 6
GeneralManager 同意了你的申请, dayNums = 100


b. 优缺点分析


从上面请假的例子中,我们可以看到,当我们需要请假的时候,我们直接调用请假的接口,无需关心处理者到底是谁,即把请求者和处理者之间的逻辑剥离开来,降低耦合度。同时,如果我们需要增加新的处理者的话,我们只需要重新组合链表即可。


有优点也必定有缺点,比如,当链表很长的时候,一级一级请求,在性能上可能会有一些影响。同时,如果我们没有正确设置处理者,可能会导致请求没有人处理。


因此,优缺点总结如下。


优点:


- 请求者与处理者降低耦合度,他们之间甚至可以互相不知道对方的存在

- 增加新的处理类很方便。


缺点:


- 对性能可能会有一定的影响,当链表很长的时候,一级一级调用,处理的时间可能会比较长。


责任链模式在 Android 中的体现


a. ViewGroup 事件传递


还记得 Android 总的事件分发机制吗,主要有三个方法,dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent 三个方法


dispatchTouchEvent ,这个方法主要是用来分发事件

onInterceptTouchEvent,这个方法主要是用来拦截事件的(需要注意的是ViewGroup才有这个方法,View没有onInterceptTouchEvent这个方法

onTouchEvent,这个方法主要是用来处理事件


requestDisallowInterceptTouchEvent(true),这个方法能够影响父View是否拦截事件,

- true 表示不拦截事件

- false 表示拦截事件


下面引用 “图解 Android 事件分发机制这一篇博客的内容



当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发。


  1. 如果dispatchTouchEvent返回 true 消费事件,事件终结。

  2. 如果dispatchTouchEvent返回 false ,则回传给父View的onTouchEvent事件处理;

    1. onTouchEvent事件返回true,事件终结;

    2. 返回false,交给父View的OnTouchEvent方法处


  3. 如果dispatchTouchEvent返回super的话,默认会调用自己的 onInterceptTouchEvent 方法

    1. 默认的情况下interceptTouchEvent回调用super方法,super方法默认返回false,所以会交给子View的onDispatchTouchEvent方法处理

    2. 如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,

    3. 如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view dispatchTouchEvent 再来开始这个事件的分发。


通过这样链式的设计,确保了每一个 View 都有机会处理 touch 事件

如果中途有 View 处理了事件,就停止处理。


b. 有序广播


Android 中的 BroastCast 分为两种,一种时普通广播,另一种是有序广播。

普通广播异步的,发出时可以被所有的接收者收到。

有序广播是根据优先级一次传播的,直到有接收者将其终止或者所有接收者都不终止它。

有序广播的这一特性与我们的责任链模式很相近,我们可以轻松地实现一种全局的责任链事件处理。


正文over~


近期文章:



日问题:

关于责任链模式,你还想到啥?


专属升级社区:《这件事情,我终于想明白了》