责任链模式是将一连串的业务职责进行细分,使多个对象各自负责其中一部分的职责,然后将这些对象连城一条链,将请求沿着这条链进行传递,直到将这个业务逻辑处理完成为止。责任链模式可以用于整条链都是在处理一个功能的场景,比如权限的校验,各自校验一部分,直到链上的节点都走完为止;也可以用在一个功能只需要链上一个节点来处理的场景,在这种场景中,请求在链上传递,直到一个节点处理了请求为止。
责任链模式的实现思路是写一个抽象的父类,在父类中定义好持有下一个自身节点的引用,然后将真正的业务处理的方法写成抽象方法,让子类去实现,在这个角度来说,责任链和模板方法模式就比较像,但是责任链的重心是后面要将这些具体的实现串在一起,形成一条链条,将请求传给第一个节点,然后一直执行下去。
责任链模式的结构图
责任链模式的结构图就比较简单,Handler定义了一个持有下一个节点的抽象类,并定义处理业务逻辑的方法,ConcreteHandler类用来实现各自不同的逻辑功能,然后在使用的时候,由客户端Client先将他们穿成一条链,然后再进行使用。
登录权限校验示例
我们来简单模拟一个登录校验的场景,用于用userId和password两个参数来登录系统,我们先校验其userId是否为空,然后再校验password是否为空,再校验用户名和密码是否匹配,最后校验用户角色是管理员还是普通用户,这些校验中只要有一个失败,就返回登录失败,成功则继续进行下一个校验,直到所有的都完成。代码示例如下:
1. 定义一个顶层父类LoginHandler
public abstract class LoginHandler {
private LoginHandler nextHandler;
public void addNext(LoginHandler handler) {
this.nextHandler = handler;
}
public LoginHandler getNextHandler() {
return this.nextHandler;
}
public abstract void authLogin(String userId, String password);
}
2. 实现四个校验的子类
/**
* 用户ID为空校验
*/
public class UserIdEmptyAuth extends LoginHandler {
@Override
public void authLogin(String userId, String password) {
if (userId == null || "".equals(userId.trim())) {
System.out.println("userId为空,请重新登录");
return;
}
if (getNextHandler() != null) {
getNextHandler().authLogin(userId, password);
}
}
}
/**
* 密码为空校验
*/
public class PasswordEmptyAuth extends LoginHandler {
@Override
public void authLogin(String userId, String password) {
if (password == null || "".equals(password.trim())) {
System.out.println("password 为空,请重新登录");
return;
}
if (getNextHandler() != null) {
getNextHandler().authLogin(userId, password);
}
}
}
/**
* 用户ID和密码匹配校验
*/
public class UserIdMatchPasswordAuth extends LoginHandler {
@Override
public void authLogin(String userId, String password) {
if (!authUserIdAndPassword(userId, password)) {
System.out.println("用户名或者密码错误,请重新登录");
return;
}
if (getNextHandler() != null) {
getNextHandler().authLogin(userId, password);
}
}
private boolean authUserIdAndPassword(String userId, String password) {
if ("user1".equals(userId) && "aaa".equals(password)) {
return true;
}
if ("user2".equals(userId) && "bbb".equals(password)) {
return true;
}
return false;
}
}
/**
* 用户权限校验
*/
public class UserIdAuthorityAuth extends LoginHandler {
@Override
public void authLogin(String userId, String password) {
if ("user1".equals(userId)) {
System.out.println("管理员用户登录成功!");
} else {
System.out.println("普通用户登录成功");
}
}
}
3. 写一个测试方法,进行测试
public class Client {
@Test
public void testChain() {
// 创建责任链
LoginHandler userIdEmptyCheck = new UserIdEmptyAuth();
LoginHandler passwordEmptyCheck = new PasswordEmptyAuth();
LoginHandler userIdMatchCheck = new UserIdMatchPasswordAuth();
LoginHandler userIdAuthCheck = new UserIdAuthorityAuth();
userIdEmptyCheck.addNext(passwordEmptyCheck);
passwordEmptyCheck.addNext(userIdMatchCheck);
userIdMatchCheck.addNext(userIdAuthCheck);
// 开始测试
testChain(userIdEmptyCheck);
}
private void testChain(LoginHandler chain) {
System.out.println("-------------------测试用户名为空------------------");
chain.authLogin(null, null);
System.out.println("-------------------测试密码为空------------------");
chain.authLogin("user1", null);
System.out.println("-------------------测试用户名和密码不匹配------------------");
chain.authLogin("user1", "bbb");
System.out.println("-------------------测试管理员------------------");
chain.authLogin("user1", "aaa");
System.out.println("-------------------测试普通用户------------------");
chain.authLogin("user2", "bbb");
}
}
测试结果如下:
-------------------测试用户名为空------------------
userId为空,请重新登录
-------------------测试密码为空------------------
password 为空,请重新登录
-------------------测试用户名和密码不匹配------------------
用户名或者密码错误,请重新登录
-------------------测试管理员------------------
管理员用户登录成功!
-------------------测试普通用户------------------
普通用户登录成功
4. 结合创建者模式,优化创建责任链过程
上面testChain()我们可以看到,在创建责任链的时候,我们先创建了很多的对象,然后又要一个一个设置到下一个节点,创建过程非常的繁琐,而且容易设置错误,我们可以用创建者模式对其进行优化,关于创建者模式的讲解,我们可以参考博客《建造者模式——创建复杂对象的方法》,其实现思路是:定义一个LoginAuthChainBuilder,利用链表的数据结构,将加入的LoginHandler按照加入顺序自然的组成一条链,然后用build()方法返回其头部节点,代码如下:
/**
* 登录校验构造器类
*/
public class LoginAuthChainBuilder {
private LoginHandler head;
private LoginHandler tail;
public LoginAuthChainBuilder addNext(LoginHandler nextHandler) {
if (head == null) {
// 第一个节点,让head和tail都指向同一个开始的节点
head = nextHandler;
tail = nextHandler;
return this;
}
// 不是第一个节点,顺序往下链接
tail.addNext(nextHandler);
tail = nextHandler;
return this;
}
public LoginHandler build() {
return head;
}
}
在client中新增一个testWithBuilder()方法,测试用构建者模式创建责任链的使用
@Test
public void testWithBuilder() {
// 创建责任链
LoginAuthChainBuilder builder = new LoginAuthChainBuilder();
LoginHandler chain = builder.addNext(new UserIdEmptyAuth())
.addNext(new PasswordEmptyAuth())
.addNext(new UserIdMatchPasswordAuth())
.addNext(new UserIdAuthorityAuth())
.build();
// 开始测试
testChain(chain);
}
其测试结果如下:
-------------------测试用户名为空------------------
userId为空,请重新登录
-------------------测试密码为空------------------
password 为空,请重新登录
-------------------测试用户名和密码不匹配------------------
用户名或者密码错误,请重新登录
-------------------测试管理员------------------
管理员用户登录成功!
-------------------测试普通用户------------------
普通用户登录成功
我们可以看到,两种方式的测试结果是一样的,但是加入了构建者后,创建责任链的方式明显简洁了很多,我们addNext的顺序,就直接反映了责任链的顺序,不用再费力检查是不是哪个节点设置错了。
5. 示例代码结构图如下:
后记
个人总结,欢迎转载、评论、批评指正