介绍
我一直感觉Android源码不能只简单的看,结合设计模式的思想再去看源码会有更深的理解。但是源码中应用某个设计模式,会根据实际情况会有一些差异。所以需要先透彻地理解设计模式。熟悉设计模式的应用场景就可以在Android源码的场景中找到相应的实现,熟悉设计模式就能够透过浩瀚的源码看到本质。
今天突然想到有关View点击事件的分发,感觉它的设计需求就很符合职责链模式(Chain of Responsibility)的应用场景。《Android源码设计模式解析与实战》中也说到了这点。所以本篇先重点介绍职责链模式。
如果已经理解该模式可以直接看下篇:
Android开发-分析ViewGroup、View的事件分发机制、结合职责链模式
职责链模式介绍
定义
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
应用场景
想象这样的一个需求场景:在公司里面申请活动费用,申请人填写表格,交给上级审批,如果活动费用数额在上级的处理范围就处理,然后返回通过请求,否则上级再交给他的上级,处理逻辑同样。最后肯定有一个人处理。
针对上面的场景,就是职责链模式的最典型应用场景。
UML图
- Handler:定义职责接口,通常在内部定义处理请求的方法,可以在这里实现后继链。
- ConcreteHandler:实际的职责类,在这里个类里面,实现在它职责范围内的请求处理,如果不处理,就继续转发请求给后继者。
- Client:客户端,组装职责链,向链上的具体对象提交请求。
图中最关键的点就是:那条从Handler出发又指向自己的线,它就是实现链式调用的关键。
代码
先上图是为了能够先看清职责链模式的大概框架。现在用实际代码和运行结果展示该模式的实现思想。
Handler抽象类
Handler抽象类作为职责接口,是所有职责对象的父类。
/**
* Created by LiCola on 2016/05/31 23:16
*/
public abstract class Handler {
/**
* 内部持有后继的职责对象
*/
protected Handler successor;
/**
* 注入后继职责对象
* @param successor
*/
public void setSuccessor(Handler successor) {
this.successor = successor;
}
/**
* 抽象父类统一请求处理方法
* 可以在方法中传值 作为判断条件 也可以不传值利用其它外部条件
*/
public abstract void handlerRequest(int cost);
}
具体的职责实现对象
为了简洁起见,只实现两个具体的职责对象,在方法内部都提供结果打印显示处理流程。
职责对象1
/**
* Created by LiCola on 2016/05/31 23:19
*/
public class ConcreteHandler1 extends Handler {
@Override
public void handlerRequest(int cost) {
/*
根据某个条件判断是否是自己的处理职责范围
一般是传入的值 或者外部数据
*/
if (cost<1000){
//如果是自己的职责范围 就在内部处理
System.out.println(this.getClass().getSimpleName()+" handler event");
}else {
//如果不在自己的职责范围 就判断是否有后继者
if (this.successor!=null){
//存在后继者 讲请求传递
System.out.println(this.getClass().getSimpleName()+" post handler");
this.successor.handlerRequest(cost);
}
}
}
}
职责对象2
/**
* Created by LiCola on 2016/05/31 23:19
*/
public class ConcreteHandler2 extends Handler {
@Override
public void handlerRequest(int cost) {
//简化流程 该对象一定能处理事件 不再下发
System.out.println(this.getClass().getSimpleName()+" handler event");
}
}
Client客户端
在这里客户端不是简单的调用,它还需要组装职责链,并发起请求
/**
* Created by LiCola on 2016/05/31 23:27
*/
public class Client {
public static void main(String[] args){
//客户端需要自己组装职责链
//构造职责对象
Handler handler1=new ConcreteHandler1();
Handler handler2=new ConcreteHandler2();
//组装链
handler1.setSuccessor(handler2);
//发起请求
handler1.handlerRequest(2000);
}
}
分析
运行上面的代码,先看打印出来的结果:
说明:
因为发出的请求不在ConcreteHandler1的处理范围,它将事件下发了,而ConcreteHandler2具有最高的权限范围,一定能处理请求。
上面的代码只是最简单的实现,只拼接了两个职责对象,根据实际情况可以有很多的职责对象。
再看一下职责链的调用过程加深印象:
模式讲解
看完上面的代码,相信你就能够理解这句话了:
客户端发出一个请求,链上的对象都有机会来处理这一请求,而客户端不需要知道谁是具体的处理对象。这样就实现了请求者和接受者之间的解耦,并且在客户端可以实现动态的组合职责链。
如果对上面的代码修改成:每个请求都不拦截,而是一步步的往下传,每一步都会处理请求对象,就可以变形成处理请求功能链。
这里提一个概念
隐式接受者:对于请求者而言,它并不知道最终的接受者是谁,但是一般情况下总会有一个对象来处理,因此称为隐式接受者
职责链的构造
刚才的代码只是示例,在客户端只简单的拼接了链。实际使用中,职责链的构造是非常重要的点。那如果构造职责链?
按照实现的角度来分:
- 客户端组装职责链,在使用时动态的组合链,称为
外部链
,我们上面的代码就是实现了外部链。 - Handler中实现链组合,可以称为
内部链
- 还可以在各个职责对象中,由对象自行决定后续处理的对象。这种方式要求每个职责对象处理进行任务处理外,还需要了解整个业务流程。
按照构造链的数据来源分,也就是决定了按照什么顺序呢来组合链的数据可以分为:
- 在程序中动态组合
- 通过外部,比如数据库来获取组合数据,这属于数据库驱动方式
- 通过流程配置文件
如果是从外部获取数据来构建链,那么在程序运行时,会读取这些数据,然后根据数据的要求来获取相应的对象组合起来。
作为一个Android开发者,看到这里是不是感觉外部数据、配置文件、隐式接受者。会不会是App项目中的
manifest
文件中每个组件的 ,和我们平常的隐式启动Activity
我也只是猜想,没有经过具体的验证证明。附上Intent 和 Intent 过滤器,大家自己去看。
职责链模式的思考
说了这么多,下面总结一下职责链模式的优缺点
优点
- 请求者和接受者松散耦合:请求者不需要知道接受者,也不需要知道如何处理。每个职责对象只负责自己的职责范围,其他的交给后继者。各个组件间完全解耦。
- 动态组合职责:职责链模式会把功能分散到单独的职责对象中,然后在使用时动态的组合形成链,从而可以灵活的分配职责对象,也可以灵活的添加改变对象职责。
职责链模式的最主要功能就是:动态组合,请求者和接受者解耦
缺点
- 产生很多细粒度的对象:因为功能处理都分散到了单独的职责对象中,每个对象功能单一,要把整个流程处理完,需要很多的职责对象,会产生大量的细粒度职责对象。
- 不一定能处理:每个职责对象都只负责自己的部分,这样就可以出现某个请求,即使把整个链走完,都没有职责对象处理它。这就需要提供默认处理,并且注意构造链的有效性
Android隐式Intent的隐患
说到职责链模式的缺点,突然想到Android开发中隐式Intent的隐患。
如果我们在代码中,想通过隐式启动处理某个用户请求,比如这样的代码
Uri uri = Uri.parse(link);
Intent intent= new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
可能的结果:当没有任何应用处理发送到 startActivity() 的隐式 Intent。如果出现这种情况,则调用将会失败,且应用会崩溃。
所以要验证 Activity 是否会接收 Intent,请对 Intent 对象调用 resolveActivity()。如果结果为非空,则至少有一个应用能够处理该 Intent,且可以安全调用 startActivity()。如果结果为空,则不应使用该 Intent。
对应的代码是这样的
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
应用场景
- 如果有多个对象可以处理同一个请求,但是具体由哪个对象处理是由运行时刻动态决定的,这种对象就可以使用职责链模式,把处理请求的对象实现成职责对象,然后构造链,当请求在这个链中传递的时候,会根据运行状态判断。
- 当不明确接受者的情况下。
- 当想要动态指定处理一个请求的对象集合
只所以把第一项说得这么细,就是因为它刚好就是我们Android开发中View的事件分发处理。手在屏幕上点击产生事件,每个View都是职责对象都可以处理事件。具体内容我会在下一篇博客详细分析。
下篇点这里: Android开发-分析ViewGroup、View的事件分发机制、结合职责链模式
总结
- 本文首先提出职责链模式概念,并用UML图和模板代码说明模式
- 模式讲解指出关键点
- 思考说明职责链模式的优缺点,引出Android隐式Intent的隐患并提出解决方案。最后指出应用场景。