责任链模式的学习与应用
定义
责任链模式(Chain of Responsibility)是多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有对象能够处理.
场景
员工想要请假,在OA系统中需要审批.
对于不同的请假天数,审批人是不一样的.小于3天,组长审批,大于三天小于7天,主管审批,大于7天小于15天CTO审批.
典型实现
public Response handle(Request req) {
if (req.day < 0 || req.day > 15) {
throw new IllegalArgumentException();
}
if (req.day <= 3) {
return new Leader().handle(req);
} else if (req.day <= 7) {
return new Director().handle(req);
} else {
return new CTO().handle(req);
}
}
实际场景可能要比这个复杂一些,比如Leader,Director,CTO可能是通过注入而不是new的.
目前的类图:
优点:
- 代码清晰明了.
缺点:
- 耦合度高,客户端代码依赖所有处理类. 如果后续想要继续添加处理类,就需要继续添加else if.
- 顺序也是定死的.如果要改顺序.就必须重新修改.
改进[责任链]
既然我们的所有处理类都实现了IHandler接口.那我们的执行流程在编译层面就是标准化的.那我们能不能通过链接各个处理类的方式,让Client依赖的实现最小呢?当然可以.和链表类似.我们可以构造一个处理器链.只需要在IHandler里添加setNext()方法即可.
改进后代码
IHandler实现类:
class Leader implements IHandler {
private IHandler next;
public Response handle(Request req) {
if (req.getDay() <= 3) {
return new Response("Leader audit");
}
return this.next.handle(req);
}
@Override
public void setNext(IHandler handler) {
this.next = handler;
}
}
其他类似,就不贴了.
业务代码:
public Response handle(Request req) {
if (req.day < 0 || req.day > 15) {
throw new IllegalArgumentException();
}
return handler.handle(req);
}
客户端代码:
public static void main(String[] args) {
IHandler handler = new Leader();
IHandler director = new Director();
IHandler cto = new CTO();
handler.setNext(director);
director.setNext(cto);
CORDemo01 demo = new CORDemo01();
demo.handler = handler;
Response response = demo.handle(new Request(6));
System.out.println(response.getMessage());
}
改进后的类图
优点
- 不再依赖所有实现.而是只依赖第一个实现.而其他的实现通过链接的方式进行组装.
- 我们已经可以通过某些方式动态的组装我们的链了.
总结
纯粹的责任链模式这样就介绍完了.
从传统的编程方式过度到责任链模式.实际上还是很容易理解了.为的就是减少调用类与处理类之间的耦合.
另外值得注意的是,纯粹的责任链模式,只有一个处理器进行处理.而其他的并不参与执行.
而我们在实际使用的场景中,比如过滤器,则是多个处理器都会参与执行.
下面就讨论一下.责任链的几个变种.
责任链模式列表方式实现
我们看到纯的责任链模式中,使用了链表的形式.并且暴露了第一个执行单元.
这种链表的方式在组装的时候如果有很多个节点,将十分繁琐.对于这一部分,有一种常见的优化方案.
是将所有的处理器按顺序放到一个List中进行处理.代码如下
IHandler实现类:
class Leader implements IHandler {
public Response handle(Request req) {
if (req.getDay() <= 3) {
return new Response("Leader audit");
}
return null;
}
}
其他类似,就不贴了.
HandlerChain
class HandlerChain {
private List<IHandler> processList = Lists.newArrayList();
public HandlerChain addHandler(IHandler handler) {
if (handler != null) {
processList.add(handler);
}
// 链式调用.
return this;
}
public void setProcessList(List<IHandler> processList) {
this.processList = processList;
}
public Response process(Request req) {
for (IHandler handler : processList) {
Response response = handler.handle(req);
if (response == null) {
continue;
}
return response;
}
throw new IllegalArgumentException();
}
}
优点
- 通过这种方式,我们可以较为轻松的组装我们的处理链.
- 通过setProcessList方法.我们也可以动态的更改处理流程.
不纯粹的责任链
过滤器
不纯粹的责任链是指,进入链之后,并不是只有一个处理器才能执行.而是所有的处理器都可能参与执行.
典型的场景就是过滤器
下面我们简单实现一个过滤器.我们有一个List的String.我们将其中长度大于1,首字母是w的字符串都挑选出来.如果用过Java8的朋友肯定知道,使用stream表达式,filter就可以很容易的实现这个需求.但是实际场景中,可能要比这个复杂的多.这里只是举个简单的例子.来说明这种不纯粹的责任链的实现.实现方式采取两种.
传统方式.利用Java8 Function的antThen组装多个Function的方式实现过滤器.
-
传统方式
接口类
public interface IFilter { List<String> filter(List<String> toFilter); }
组装类
class FilterChain { private List<IFilter> filterList; public List<String> process(List<String> toFilter) { for (IFilter filter : filterList) { toFilter = filter.filter(toFilter); } return toFilter; } }
两个实现类
class LengthFilter implements IFilter { @Override public List<String> filter(List<String> toFilter) { List<String> result = Lists.newArrayList(); for (String str : toFilter) { if (StringUtils.isNotBlank(str) && str.length() > 1) { result.add(str); } } return result; } } class StartCharFilter implements IFilter { @Override public List<String> filter(List<String> toFilter) { List<String> result = Lists.newArrayList(); for (String str : toFilter) { if (StringUtils.isNotBlank(str) && str.startsWith("w")) { result.add(str); } } return result; } }
值得注意的是有很多模板代码.这些模板代码在传统方式下,可以通过模板模式进行简化.
-
Java8 Function方式
public class HandlerChain { private static UnaryOperator<List<String>> filterByLength = (List<String> text) -> text.stream().filter(item -> item.length() > 1).collect(Collectors.toList()); private static UnaryOperator<List<String>> spellCheckerProcessing = (List<String> text) -> text.stream().filter(item -> item.startsWith("w")).collect(Collectors.toList()); private static Function<List<String>, List<String>> filterChain = filterByLength.andThen(spellCheckerProcessing); public static List<String> filter(List<String> input) { return filterChain.apply(input); } }
当然在这个需求下,你也可以直接使用stream.将两个filter连接起来.
但是如果你考虑纯粹的责任链模式.上面的Java8 Funciton的方式.确实可以节省很多代码和类.
拦截器
Servlet,Struts2,Spring MVC,都有拦截器.而这些拦截器的实现.则也是不纯粹的责任链的一种.因为所有的拦截器都会执行一遍.如果确认拦截,则直接返回.如果通过,则继续执行.看起来和过滤器很像.
但是拦截器的功能不止于此,拦截器不仅支持preAction操作,还支持postAction操作.我们想要讨论的就是这种设计是如何编码实现的.
接口定义
public interface IInterceptor {
boolean before();
void after();
}
拦截器与目标对象的组装类
public class MainExecuteProxy {
private Object target;
private List<IInterceptor> interceptorList = Lists.newArrayList();
public MainExecuteProxy addInterceptor(IInterceptor interceptor) {
interceptorList.add(interceptor);
return this;
}
public String execute() {
return process(interceptorList.iterator(), target);
}
private String process(Iterator<IInterceptor> interceptorIterator, Object target) {
if (interceptorIterator.hasNext()) {
IInterceptor interceptor = interceptorIterator.next();
boolean before = interceptor.before();
if (!before) {
return "执行失败";
}
String res = process(interceptorIterator, target);
interceptor.after();
return res;
} else {
// do action.
System.out.println("real action");
return "执行成功";
}
}
}
代码分析
MainExecuteProxy类里需要有一个目标对象,用来执行目标方法.而interceptorList就是我们之前讲到的责任链.那这种拦截是怎么实现的呢.主要的逻辑就是process方法.我们的逻辑很简单.就是当我们的所有before方法都为true的时候执行目标对象的方法.当有一个失败的时候,就立马拦截.而在拦截并且执行完目标方法后,我们需要在一个一个的反向执行after方法.因为before/after是配对的.
为了达到这种目的,我们使用了递归.原因就是递归是可以保留递归现场的.所以的方法在调用的时候进入到方法栈,先进去的在下面,后进去的在上面.所以当我们在执行完目标方法一层一层返回的时候,也是后调用的先执行after.达到了效果.
Client代码
public class Main {
public static void main(String[] args) {
MainExecuteProxy executeProxy = new MainExecuteProxy();
String result = executeProxy.addInterceptor(new IInterceptor() {
@Override
public boolean before() {
System.out.println("aaa");
return true;
}
@Override
public void after() {
System.out.println("aaa");
}
}).addInterceptor(new IInterceptor() {
@Override
public boolean before() {
System.out.println("bbb");
return false;
}
@Override
public void after() {
System.out.println("bbb");
}
}).execute();
System.out.println(result);
}
}
执行结果:
aaa
bbb
aaa
执行失败
先进入拦截器一的before,输出aaa,返回true,继续执行
进入拦截器二的before,输出bbb,返回false.停止继续执行,返回执行失败.
回到上一层的代码.继续执行拦截器一的after.输出aaa.然后返回.
和预期的相符.
总结
拦截器模式,无论纯粹或变种,在实际开发和框架中都会时常看到,需要我们掌握.并且在合适的时机予以使用.