一、责任链模式介绍
1. 解决的问题
主要解决请求发送者和处理者的解耦问题。
2. 定义
责任链模式是一种行为设计模式,允许将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。
3. 应用场景
- 当程序需要不同方式处理不同种类请求,而且请求类型和顺序预先未知时,可以使用责任链模式。
- 当必须按照顺序执行多个处理者时,可以使用责任链模式。
- 如果所需处理者及其顺序必须在运行时进行改变,可以使用责任链模式。
二、责任链模式优缺点
1. 优点
- 可以控制请求处理的顺序。
- 单一职责原则:可将发起请求和执行操作的类进行解耦。
- 开闭原则:可以在不更改现有代码的情况下在程序中新增处理者。
2. 缺点
- 部分请求可能未被处理。
三、责任链模式应用实例:请求中间件
1. 实例场景
在社区网站中,通常会给不同操作设置不同的权限。比如查看帖子不许任何权限,发布帖子需要验证用户是否登录,且用户是否为正常注册用户。
通常需要验证几个步骤:用户输入是否合法 -> 是否存在该用户 -> 用户状态是否正常。
今天,就以该场景为例来介绍责任链模式在请求中间件的使用。
2. 责任链模式实现
2.1 工程结构
chain-of-responsibilty-pattern
└─ src
├─ main
│ └─ java
│ └─ org.design.pattern.chain
│ ├─ Middleware.java
│ └─ impl
│ ├─ InputLegalMiddleware.java
│ ├─ UserExistMiddleware.java
│ └─ AccountActivatedMiddleware.java
└─ test
└─ java
└─ org.design.pattern.chain
└─ MiddlewareTest.java
2.2 代码实现
2.2.1 中间件抽象类
/**
* 中间件抽象类
*/
public abstract class Middleware {
/**
* 下一个节点
*/
private Middleware next;
/**
* 设置下一个中间件节点
*
* @param next 下一个中间件节点
* @return Middleware
*/
public Middleware setNext(Middleware next) {
this.next = next;
return this;
}
/**
* 验证
*
* @param userId 用户id
* @param email 邮箱
* @return boolean
*/
public boolean auth(String userId, String email) {
if (ObjectUtils.isEmpty(next)) {
return true;
}
return next.auth(userId, email);
}
}
2.2.2 中间件实现类
输入验证中间件
/**
* 输入验证中间件
*/
@Slf4j
public class InputLegalMiddleware extends Middleware {
/**
* 验证
*
* @param userId 用户id
* @param email 邮箱
* @return boolean
*/
@Override
public boolean auth(String userId, String email) {
// 模拟验证输入不合法
if (ObjectUtils.isEmpty(userId) || ObjectUtils.isEmpty(email)) {
log.error("输入不合法,用户{},邮箱{}", userId, email);
return false;
}
return super.auth(userId, email);
}
}
用户存在中间件
/**
* 用户存在中间件
*/
@Slf4j
public class UserExistMiddleware extends Middleware {
/**
* 验证
*
* @param userId 用户id
* @param email 邮箱
* @return boolean
*/
@Override
public boolean auth(String userId, String email) {
// 模拟验证用户不存在
if (!userId.equals("yiyufxst")) {
log.error("用户{}不存在", userId);
return false;
}
return super.auth(userId, email);
}
}
验证账户激活中间件
/**
* 验证账户激活中间件
*/
@Slf4j
public class AccountActivatedMiddleware extends Middleware {
/**
* 验证
*
* @param userId 用户id
* @param email 邮箱
* @return boolean
*/
@Override
public boolean auth(String userId, String email) {
// 模拟邮箱未验证激活
if (!email.equals("yiyufxst@qq.com")) {
log.error("用户{}邮箱{}未激活", userId, email);
return false;
}
return super.auth(userId, email);
}
}
2.3 测试验证
2.3.1 测试验证类
@Slf4j
public class MiddlewareTest {
@Test
public void testMiddleware() {
// 输入合法 -> 用户存在 -> 账户已激活
Middleware loginAuth = new InputLegalMiddleware().setNext(
new UserExistMiddleware().setNext(new AccountActivatedMiddleware())
);
boolean isLogin = loginAuth.auth("yiyu", "");
Assert.assertFalse(isLogin);
isLogin = loginAuth.auth("yiyu", "xx");
Assert.assertFalse(isLogin);
isLogin = loginAuth.auth("yiyufxst", "yiyufxst@xx.com");
Assert.assertFalse(isLogin);
String userId = "yiyufxst";
String email = "yiyufxst@qq.com";
isLogin = loginAuth.auth(userId, email);
Assert.assertTrue(isLogin);
log.info("用户{},邮箱{}已激活,可正常使用", userId, email);
}
}
2.3.2 测试结果
16:17:51.601 [main] ERROR o.d.p.c.impl.InputLegalMiddleware - 输入不合法,用户yiyu,邮箱
16:17:51.604 [main] ERROR o.d.p.chain.impl.UserExistMiddleware - 用户yiyu不存在
16:17:51.604 [main] ERROR o.d.p.c.i.AccountActivatedMiddleware - 用户yiyufxst邮箱yiyufxst@xx.com未激活
16:17:51.604 [main] INFO o.d.pattern.chain.MiddlewareTest - 用户yiyufxst,邮箱yiyufxst@qq.com已激活,可正常使用
Process finished with exit code 0
四、责任链模式结构
- 处理者(Handler)声明了所有具体处理者的通用接口。该接口通常仅包含单个方法用于请求处理,但有时还会包含一个设置链上下个处理者的方法。
基础处理者(Base Handler)是一个可选的类,可以将所有处理者公用的样本代码放置在其中。
通常情况下,该类中定义了一个保存下个处理者引用的成员变量。客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。
具体处理者(Concrete Handlers)包含处理请求的实际代码。每个处理者接收到请求后,都必须决定是否进行处理,以及是否沿着链传递请求。
处理者通常是独立且不可变的,需要通过构造函数一次性地获取所有必要的数据。
- 客户端(Client)可根据程序逻辑一次性或动态地生存链。不过,请求可发送给链上任意一个处理者,而不是一定要发给第一个处理者。
设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想去优化代码,就能构建出更合理的代码。
源码地址:https://github.com/yiyufxst/design-pattern-java
参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei/itstack-demo-design
深入设计模式:https://refactoringguru.cn/design-patterns/catalog
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。