Android 开发中常用设计模式解读之-责任链(拦截器)

模式定义与设计解读

设计模式晦涩的定义总是难懂,但同时这些定义又有着独特的涵义。本文想通过最直观的例子,把这些晦涩的定义反应在代码层面上。代码是设计模式最直观的表达,当你看不懂定义时,代码会说话。希望这篇解读可以帮助到你。预计阅读10分钟。

定义

责任链模式 :使多个对象有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

注意一下加粗的关键字,解读会重新定义这些难懂的定义。

解读

在生活经常存在一种场景,完成一件事情,需要串行执行几个步骤。比如把大象装冰箱,需要三个步骤。

  1. 把冰箱门打开
  2. 把大象装冰箱
  3. 把冰箱门关上

现在把这件事情抽象成代码,并与上文中的定义对应。

  • 请求指把大象装冰箱这件事情,对应一个数据结构 RequestContext
/**
 * 请求上下文
 */
public class RequestContext {
  
  public Elephant mElephant;//大象
  
  public Refrigerator mRefrigerator;//冰箱

  public RequestContext(Elephant elephant, Refrigerator refrigerator) {
    mElephant = elephant;
    mRefrigerator = refrigerator;
  }

  public static class Elephant{
  }

  public static class Refrigerator{
  }
  
}
  • 一条链比较好理解,但是比较难抽象。既然做这件事需要串行好几个步骤,链条上自然也是围绕请求展开,既需要有获取请求的方法RequestContext getContext(),也需要有执行请求的方法procceed(RequestContext context)。这个方法有开始执行的意思,也有请求串行传递后继续执行的意思。

proceed 英 [prə’siːd]
vi. 开始;继续进行;发生;

/**
 * 请求链
 */
public interface Chain {
  /**
   * 获取请求实体
   */
  RequestContext getContext();

  /**
   * 开始或继续执行请求。
   */
  void proceed(RequestContext context);
  
}
  • 对象指1.2.3操作大象和冰箱的三个步骤,可以把每次步抽象成一个处理请求的对象,万物皆对象。现在我们已经把请求封装在了链条里,那对于每个操作步骤,都直接来处理链条上的请求即可。
/**
 * 处理者
 */
public interface Handler {

  /**
   * 处理链条上的请求
   */
  void handle(Chain chain);
  
}

  • 发送者和接收者其实是指操作步骤的动作。每个操作步骤都会有以下流程
  1. 先接收请求(接收)
  2. 处理请求 (处理)
  3. 通知Chain,继续执行请求。(发送)

每个操作步骤只需要关注自己需要如何操作请求,而不需要关心操作完成后下个步骤是什么。只需要通知Chain,我执行完毕了,可以继续执行下一步了。从而避免了发送者和接收者之间的耦合关系。那么如何解耦呢,自然是链条来解决。链条上持有一系列操作步骤,存储成List<Handler>,并且记录当前执行到第几步int index,再结合之前定义的Chain接口,Chain实现类如下

/**
  * 链实现类
  */
public class ProcessChain implements Chain {

  public List<Handler> mProcessors;
  public RequestContext mChainContext;
  public int mIndex;

  public ProcessChain(List<Handler> processors, int index, RequestContext chainContext) {
    mProcessors = processors;
    mIndex = index;
    mChainContext = chainContext;
  }

  @Override
  public RequestContext getContext() {
    return mChainContext;
  }

  @Override
  public void proceed(RequestContext processContext) {
    if (mProcessors.size() > mIndex) {
      //获取当前处理者
      Handler processor = mProcessors.get(mIndex);

      //更新index 与 Context
      ProcessChain nextChain = new ProcessChain(mProcessors, mIndex + 1, processContext);

      //处理者执行处理步骤
      processor.handle(nextChain);
    }
  }
}

proceed方法可能会觉得有些绕,其实道理很简单。着重理解一下nextChain即可,这里是重新构造了一个Chain,其实也可以理解为更新了一下Chain的数据,将index+1,以及更新RequestContext 。接下来讲到Processor的实现类,二者结合起来理解会更容易nextChain

  • Handler实现类。接收的概念好理解,就是接口方法handle(Chain chain)中的参数。那么什么是发送呢,我们回到上文nextChain,实际handle(Chain chain)方法接收的参数就是下个节点的Chain,我们只需要在handle()方法中处理完毕后,继续调用chain.proceed()方法,通知链条继续执行就可以了。请求就这样被发送出去了。
/**
 * 打开冰箱
 */
public class HandlerOpenRefrigerator implements Handler {
  @Override
  public void handle(Chain chain) {
    RequestContext requestContext = chain.getContext();
    //处理具体操作
    if(!requestContext.mRefrigerator.isOpen()) {
      requestContext.mRefrigerator.open();
    }
    //处理完毕 继续执行下个操作
    chain.proceed(requestContext);
  }
}
  • 拦截器 当我们执行到第二步时,需要装大象,如果这时候门没打开怎么办?任务执行不下去,我们可以选择中断链条,退出本次串行任务。这种链条上的任务传递和适当时机终止结合起来就是我们经常说的拦截器拦截器就是基于责任链模式,每个节点有自己的职责,同时可以选择是否把任务传递给下一个环节
/**
 * 移动大象
 */
public class MoveElephantHandler implements Handler {
  @Override
  public void handle(Chain chain){
    RequestContext requestContext = chain.getContext();
    //处理具体操作
    if(requestContext.mRefrigerator.isOpen()) {
      requestContext.mElephant.move();
      //处理完毕 继续执行下个操作
      chain.proceed(requestContext);
    }else{
      //发生异常 中断链条
      chain.abort();
    }
  }
}

讲到这里再回头读一下最开始定义的那句话。你会有不一样的理解。如果没有,自己敲一遍代码再试试。

  • 运行 依次构造请求,责任链,然后开始任务。
public class MyClass {
  public static void main(String[] args) {
    //构造请求
    RequestContext requestContext = new RequestContext(new RequestContext.Elephant(),new RequestContext.Refrigerator());

    //构造处理步骤
    List<Handler> list = new ArrayList<>();
    
    //你也可以不加打开冰箱试试结果
    list.add(new OpenRefrigeratorHandler());
    list.add(new MoveElephantHandler());
    list.add(new CloseRefrigeratorHandler());
    
    //执行任务
    ProcessChain processChain = new ProcessChain(list,0,requestContext);
    processChain.proceed(requestContext);
  }
}

Demo地址

思想理解与实践运用

理解

责任链重点是在解耦,如何将一个任务的不同步骤之间没有耦合关系,每个步骤专注负责自身,而不需要关心其他步骤,以及其他步骤的变化,这些变化可以交给更高层次来控制,而不是在任务节点上控制。比如上述例子中,如果装大象上线后发现很容易失败,需要先把大象切开(一个残忍的举例),那么只需要在控制层添加list.add(new CutElephantHandler)即可。这种修改对于既有的代码侵略性极低,因为步骤之间解耦很彻底,并且这种可插拔式的代码,组合性极强。使得代码的扩展性和可维护性极高。

运用

  • 框架中的运用OkHttp中有使用Interceptor就是一个经典的例子。来看看源码中的定义
/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;
    
  }
}

是不是很熟悉。Okhttp中拦截器就是责任链模式运用。我们在进行应用开发的时候都会在请求中增加一些通用信息,比如在 header 中增加用户的token信息等等以及在请求的过程中修改请求的 requestresponse。那么我们就可以定义不同的Interceptor来处理。这些拦截器之间没有任何关联,只关注自己处理过程。现在回头再去看看一些OKhttp的源码解读应该就很容易理解其精髓了。

  • 实际开发中的运用:除了在一些既有的框架中使用,我们在Andorid实际开发场景中如何运用呢。再举个在项目中使用过的场景。我们的App,在HomeActivity启动后,会依次做几件事情
  1. 检测升级,出现弹窗1
  2. 弹窗1关闭后,出现引导Window
  3. 引导图关闭后,出现选择语言弹窗。

如果需要是一步一步加上来的。比如第一个版本,我们只有检测升级。代码一定很直接,onCreate()方法里showUpgradeDialog()。当来了第二个需求,代码直接写就会变成这样。
在升级弹窗的Dialog onDismissListenershowGuideWindow(),如果第一步未检测到升级,还需要判断if(!needUpgrade)直接showGuideWindow()。代码写到这里,这两个步骤已经耦合在一起了。引导window是否出现,跟检测弹窗几乎绑定在一起。如果有一天产品脑洞大开,我们先出引导再升级吧!想想你的代码会怎么改。
window的 onDismissListener 里showUpgradeDialog()?如果使用责任链模式呢?在控制层换一下顺序就好了。

  • 大家可以尝试找一下项目里类似的需求,一些任务需要串行执行的地方,如果耦合严重,试着重构一下。

总结

设计模式在开发实践中,对代码的可读,可扩展,可维护性起到重要作用。在面试中,经常会和面试者聊聊设计模式,大部分面试者都停留在设计概念本身,如果能阐述更多对设计模式的理解与在实践中的运用,相信会对你的面试有帮助。本文也给大家举一个设计模式学习的例子,从模式学习,思想理解到实践运用。回头再读一下本文重点:

  • 责任链模式 :使多个对象有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

如果还没有新的理解,留言给我。

    原文作者:kenan
    原文地址: https://www.jianshu.com/p/d57abb5b87f3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞