什么是责任链模式
责任链模式(Chain of Responsibility Pattern)是指将链中的每一个节点看作是一个对象,每个节点处理的请求均不同,且每个节点内部自动维护了一个下一个节点对象。当一个请求在链路的头部发出时,会沿着链的路径依次传递给每一个节点对象,直到有对象处理这个请求为止。
责任链模式属于行为型模式。
写法示例
Talk is cheap,Show me the code。我们就以一个登录校验账号密码,角色,权限等信息的功能为例,直接来看一下责任链模式是怎么写的。
登录用户信息类
首先我们创建一个登录用户信息类:
package com.zwx.design.pattern.chainOfResponsibility;public class LoginUser { private String loginName; private String password; private String roleName; private String permission; public String getLoginName() { return loginName; } public void setLoginName(String loginName) { this.loginName = loginName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; }}
Handler抽象类
创建一个Handler抽象类,这个类维护了链路中的下一个对象,并且将真正处理逻辑的方法doHandler只进行了抽象定义,具体留给实现类去实现:
package com.zwx.design.pattern.chainOfResponsibility;public abstract class MyHandler { protected MyHandler next; public void next(MyHandler handler){ this.next = handler; } public abstract void doHandler(LoginUser loginUser);}
链路节点Handler实现类
接下来就是创建具体的实现类来实现MyHandler类。链中的每个节点只处理一件事,所以这个示例中我们可以拆分分三个节点,一个校验账号密码,一个校验角色,一个校验角色。
校验账号密码Handler
创建一个节点用来校验账号密码:
package com.zwx.design.pattern.chainOfResponsibility;import org.apache.commons.lang3.StringUtils;public class VerifyAccountHandler extends MyHandler { @Override public void doHandler(LoginUser loginUser) { if (StringUtils.isBlank(loginUser.getLoginName())){ System.out.println("用户名不能为空"); return; } if (StringUtils.isBlank(loginUser.getPassword())){ System.out.println("密码不能为空"); return; } if (!loginUser.getPassword().equals("123456")){ System.out.println("密码不正确"); return; } System.out.println("账号密码校验通过"); next.doHandler(loginUser); }}
注意最后一句话,next.doHandler(loginUser)是用来调用链路中下一个节点的处理方法
校验角色Handler
新增一个校验角色Handler
package com.zwx.design.pattern.chainOfResponsibility;public class VerifyRoleHanlder extends MyHandler { @Override public void doHandler(LoginUser loginUser) { if(!"admin".equals(loginUser.getRoleName())){ System.out.println("角色信息有误"); return; } System.out.println("角色信息校验通过"); next.doHandler(loginUser); }}
同样的,这里也需要调用下一个节点的处理方法
校验权限Handler
新增一个校验权限的Handler:
package com.zwx.design.pattern.chainOfResponsibility;public class VerifyPermissionHanlder extends MyHandler { @Override public void doHandler(LoginUser loginUser) { if (!"admin".equals(loginUser.getPermission())){ System.out.println("暂无权限"); return; } System.out.println("权限校验通过,登录成功"); }}
因为permission已经是最后一个节点,所以这里不需要再继续制定下一个节点了,内部也没有再维护下一个节点对象了。
测试运行结果
现在让我们来看一下应该如何调用上面的示例:
package com.zwx.design.pattern.chainOfResponsibility;public class TestChain { public static void main(String[] args) { MyHandler accountHandler = new VerifyAccountHandler(); MyHandler roleHanlder = new VerifyRoleHanlder(); MyHandler permissionHanlder = new VerifyPermissionHanlder(); accountHandler.next(roleHanlder); roleHanlder.next(permissionHanlder); LoginUser loginUser = new LoginUser(); loginUser.setLoginName("孤狼1号"); loginUser.setPassword("123"); loginUser.setRoleName("admin"); loginUser.setPermission("admin"); accountHandler.doHandler(loginUser);//从起点开始调用 }}
输出结果:
密码不正确
如果将密码修改为正确密码123456,则输出如下结果:
账号密码校验通过角色信息校验通过权限校验通过,登录成功
和传统写法对比
我们先来看下传统的这种登录逻辑的写法:
package com.zwx.design.pattern.chainOfResponsibility;import org.apache.commons.lang3.StringUtils;public class LoginService { public void login(LoginUser loginUser){ //1.校验账号密码 if (StringUtils.isBlank(loginUser.getLoginName())){ System.out.println("用户名不能为空"); return; } if (StringUtils.isBlank(loginUser.getPassword())){ System.out.println("密码不能为空"); return; } if (!loginUser.getPassword().equals("123456")){ System.out.println("密码不正确"); return; } //2.角色 if(!"admin".equals(loginUser.getRoleName())){ System.out.println("角色信息有误"); return; } //3.校验权限 if (!"admin".equals(loginUser.getPermission())){ System.out.println("暂无权限"); return; } System.out.println("校验通过,登录成功"); }}
看起来写法上似乎比通过责任链模式写法简单明了,但是一堆业务代码全部堆在一起,而且我们示例中的逻辑校验比较简单,如果逻辑变得很复杂,那么将各种逻辑校验做一个解耦拆分对后期维护是非常有利的。
责任链模式结合建造者模式
上面的示例写法中,最后在调用过程中有点不是很优雅,由此我们联想到了建造者模式的链式写法,接下来让我们结合建造者模式来对其进行改写。
改写Handler抽象类
改写时我们只需要对顶层抽象类进行改写:
package com.zwx.design.pattern.chainOfResponsibility.build;import com.zwx.design.pattern.chainOfResponsibility.LoginUser;public abstract class BuildHandler { protected BuildHandler next; public void next(BuildHandler handler){ this.next = handler; } public abstract void doHandler(LoginUser loginUser); public static class Builder{ private BuildHandler head; private BuildHandler tail; public Builder addHanlder(BuildHandler handler){ if (null == head){//head==null表示第一次添加到队列 head = this.tail = handler; return this; } this.tail.next(handler);//原tail节点指向新添加进来的节点 this.tail = handler;//新添加进来的节点设置为tail节点 return this; } public BuildHandler build(){ return this.head; } }}
这个类中,我们通过一个静态内部类Builder来讲链构造成一个队列。
测试运行结果
其他三个类只需要实现改造之后的BuilderHanlder类,其他不需要修改,那么我们来看看现在的测试类又应该如何调用:
package com.zwx.design.pattern.chainOfResponsibility.build;import com.zwx.design.pattern.chainOfResponsibility.*;public class TestBuildChain { public static void main(String[] args) { LoginUser loginUser = new LoginUser(); loginUser.setLoginName("孤狼1号"); loginUser.setPassword("123456"); loginUser.setRoleName("admin"); loginUser.setPermission("admin"); BuildHandler.Builder builder = new BuildHandler.Builder(); builder.addHanlder(new VerifyAccountHandler()) .addHanlder(new VerifyRoleHanlder()) .addHanlder(new VerifyPermissionHanlder()); builder.build().doHandler(loginUser); }}
输出结果:
账号密码校验通过角色信息校验通过权限校验通过,登录成功
可以看到,改写之后在调用时会优雅很多。
责任链模式角色
从上面的示例中,可以明确,责任链模式只有两个角色:
- 1、抽象处理者(Handler)::定义一个请求处理的方法,并维护一个下一个处理节点的Handler对象
- 2、具体处理者(ConcreteHandler):对请求就行处理,只处理自己部分,处理完之后可以进行转发
责任链模式适用场景
责任链模式主要是解耦了请求与处理,用户只需要将请求发送到链上即可,无需关心请求的具体内容和处理细节,请求会自动进行传递直至有节点进行处理。可以适用于如下场景:
- 1、多个对象可以处理同一请求,但具体由哪个对象处理则在运行时动态决定。
- 2、在不明确指定接收者的情况下,向多个对象中的一个提交请求
- 3、可以动态指定一组对象的处理请求。
责任链模式源码中体现
责任链模式应用比较广泛的就是拦截器。
我们先一下Servlet中的J2EE规范定义的一个拦截器接口:
我们发现这脸只有一个doFilter方法,并没有维护一个链里面的下一个对象。那么这个是怎么实现链路传递的呢?
我们看一下Spring的实现MockFilterChain:
从上面两段代码可以发现,子类通过一个List来构建“链路”,最终调用的时候就是通过遍历List来实现“链路”传递。
责任链模式优缺点
任何一个设计模式都有优点和缺点,那么责任链模式有何优缺点呢?
优点
- 1、将请求与处理解耦
- 2、请求处理者(链路中的节点)只需关注自己感兴趣的请求进行处理,对于不感兴趣或者无法处理的请求直接转发给下一个处理者
- 3、具备链式传递请求的功能,请求发送者无需知晓链路结构,只需等待请求处理结果
- 4、链路结构灵活,可以通过改变链路结构动态的新增或者删减责任
- 5、易于扩展新的请求处理类,符合开闭原则
缺点
- 1、如果责任链的链路太长或者处理时间过程,会影响性能。
- 2、如果节点对象存在循环引用时,会造成死循环,导致系统崩溃
总结
本文介绍了责任链模式的基本用法,并通过一个示例将责任链模式和建造者模式结合起来使用,使得代码更加优雅,同时也介绍了Spring中对责任链模式的应用,希望通过本文的学习,大家可以更好地理解责任链模式的原理,可以在合适的场景中进行实际应用。