模式定义与设计解读
设计模式晦涩的定义总是难懂,但同时这些定义又有着独特的涵义。本文想通过最直观的例子,把这些晦涩的定义反应在代码层面上。代码是设计模式最直观的表达,当你看不懂定义时,代码会说话。希望这篇解读可以帮助到你。预计阅读10分钟。
定义
责任链模式 :使多个对象有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
注意一下加粗的关键字,解读会重新定义这些难懂的定义。
解读
在生活经常存在一种场景,完成一件事情,需要串行执行几个步骤。比如把大象装冰箱,需要三个步骤。
- 把冰箱门打开
- 把大象装冰箱
- 把冰箱门关上
现在把这件事情抽象成代码,并与上文中的定义对应。
- 请求指把大象装冰箱这件事情,对应一个数据结构
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);
}
- 发送者和接收者其实是指操作步骤的动作。每个操作步骤都会有以下流程
- 先接收请求(接收)
- 处理请求 (处理)
- 通知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);
}
}
思想理解与实践运用
理解
责任链重点是在解耦,如何将一个任务的不同步骤之间没有耦合关系,每个步骤专注负责自身,而不需要关心其他步骤,以及其他步骤的变化,这些变化可以交给更高层次来控制,而不是在任务节点上控制。比如上述例子中,如果装大象上线后发现很容易失败,需要先把大象切开(一个残忍的举例),那么只需要在控制层添加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信息等等以及在请求的过程中修改请求的 request
和 response
。那么我们就可以定义不同的Interceptor
来处理。这些拦截器之间没有任何关联,只关注自己处理过程。现在回头再去看看一些OKhttp的源码解读应该就很容易理解其精髓了。
- 实际开发中的运用:除了在一些既有的框架中使用,我们在Andorid实际开发场景中如何运用呢。再举个在项目中使用过的场景。我们的App,在HomeActivity启动后,会依次做几件事情
- 检测升级,出现弹窗1
- 弹窗1关闭后,出现引导Window
- 引导图关闭后,出现选择语言弹窗。
如果需要是一步一步加上来的。比如第一个版本,我们只有检测升级。代码一定很直接,onCreate()
方法里showUpgradeDialog()
。当来了第二个需求,代码直接写就会变成这样。
在升级弹窗的Dialog onDismissListener
里showGuideWindow()
,如果第一步未检测到升级,还需要判断if(!needUpgrade)
直接showGuideWindow()
。代码写到这里,这两个步骤已经耦合在一起了。引导window是否出现,跟检测弹窗几乎绑定在一起。如果有一天产品脑洞大开,我们先出引导再升级吧!想想你的代码会怎么改。
在window的 onDismissListener 里showUpgradeDialog()
?如果使用责任链模式呢?在控制层换一下顺序就好了。
- 大家可以尝试找一下项目里类似的需求,一些任务需要串行执行的地方,如果耦合严重,试着重构一下。
总结
设计模式在开发实践中,对代码的可读,可扩展,可维护性起到重要作用。在面试中,经常会和面试者聊聊设计模式,大部分面试者都停留在设计概念本身,如果能阐述更多对设计模式的理解与在实践中的运用,相信会对你的面试有帮助。本文也给大家举一个设计模式学习的例子,从模式学习,思想理解到实践运用。回头再读一下本文重点:
- 责任链模式 :使多个对象有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
如果还没有新的理解,留言给我。