责任链模式:构建灵活的请求处理流程
- 一、责任链模式简介
-
- [1.1 概念](#1.1 概念)
- [1.2 模式类型](#1.2 模式类型)
- [1.3 优点](#1.3 优点)
- [1.4 缺点](#1.4 缺点)
- 二、责任链模式的模式动机
- 三、模式结构
- 四、责任链模式的实现
-
- [4.1 需求](#4.1 需求)
- [4.2 实现步骤](#4.2 实现步骤)
- [4.3 具体实现](#4.3 具体实现)
-
- [1. 用户账户类](#1. 用户账户类)
- [2. 校验器父类](#2. 校验器父类)
- [3. 校验器子类](#3. 校验器子类)
- [4. 客户端测试](#4. 客户端测试)
- [5. 运行结果](#5. 运行结果)
- 五、责任链模式的应用场景
-
- [5.1 适用场景](#5.1 适用场景)
- [5.2 应用实例](#5.2 应用实例)
一、责任链模式简介
1.1 概念
责任链模式(Chain of Responsibility Pattern
) 是一种常用的设计模式,属于行为型模式 的一种。在责任链模式中,多个对象都有机会处理请求,从而形成一条链:请求从一个对象传递到下一个对象,以此类推。这种模式允许请求的发送者和接收者解耦。在责任链模式中,请求可以被多个对象处理,知道其中一个对象负责处理它为止。
1.2 模式类型
行为型设计模式
1.3 优点
- 解耦:请求的发送者和接收者解耦,发送者不需要知道请求的处理者是谁,只需要将请求发送给链中的第一个处理者。
- 动态组合:责任链可以动态组合,使用配置设置责任链的顺序及是否出现;可以随时对责任链排序,随时增加拆除责任链中的某个请求对象。
- 单一职责原则:责任链模式符合单一职责原则,每个处理者只负责处理自己的业务逻辑,使得系统更容易扩展和维护。
- 满足开闭原则: 新增或删除处理者时,无需修改现有代码,满足开闭原则。
1.4 缺点
- 性能: 由于责任链模式中的请求在链条上依次传递,如果责任链过长或者处理者处理能力不足,可能导致请求处理时间过长,影响系统性能。
- 请求处理不保证被接收: 责任链模式中,某个请求可能因为未被任何处理者处理而被忽略,这可能导致系统无法处理某些请求。
- 调试困难: 责任链模式中由多个处理者功能处理请求,对于一个请求的处理过程和结果跟踪可能较为困难,尤其当责任链较长或者处理者较多时。
- 可能造成循环调用 : 在设计责任链模式时,如果处理者之间的联系设置不当,有可能造成循环调用,导致系统陷入死循环。
二、责任链模式的模式动机
在软件开发过程中,一个请求可能需要多个不同的处理对象。传统的处理方式是,用一个巨大的条件分支语句(if-else
或 switch
)来决定每个请求的处理者。这种方式的问题在于,处理逻辑与处理对象耦合度高,违反了开放封闭原则,同时使得系统难以维护和扩展。
责任链模式
提供了一种更优雅的方式来解决这个问题。通过构建一个链式的处理对象集合,每个处理对象只关注自己责任范围内的请求,对于不属于自己处理范围的请求,就传递给链上的下一个对象处理。这样,可以将请求的发送者和接收者进行有效解耦,提高系统的灵活性和可扩展性。
三、模式结构
责任链模式主要包含以下两个核心组成部分:
- 处理者 (
Handler
):定义一个处理请求的接口,包含处理请求的方法和一个指向链上下一个处理者的引用。 - 具体处理者 (
Concrete Handler
):实现处理者接口,处理它负责的请求,可以访问它的继任者。如果可处理该请求,就处理,否则将该请求转发给它的继任者。
四、责任链模式的实现
4.1 需求
假设有一个需求:账号注册
账号注册时进行校验,先校验账号、再校验密码,最后校验手机号等。
校验规则:
- 校验用户名,用户名不能为空,长度不能超过20个字符
- 校验密码,密码不能为空,长度不能超过16个字符,必须包含数字和字母
- 校验手机号,手机号不能为空,必须时11位数字
4.2 实现步骤
- 创建三个校验类,
UsernameVerify
,PasswordVerify
,PhoneNumberVerify
- 实现
Verify
接口,并设置nextVerify
属性,用于设置下一个校验类 - 创建
UserAccount
类,用于保存用户输入的账号信息 - 创建责任链,并设置第一个校验类为
usernameVerify
,并设置其nextVerify
属性为passwordVerify
- 调用
deploy
方法,传入UserAccount
实例,开始验证 - 验证失败,则返回错误信息,否则返回成功信息
4.3 具体实现
1. 用户账户类
java
public class UserAccount {
private String username;
private String password;
private String phoneNumber;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
2. 校验器父类
java
public abstract class Verify {
protected Verify verify;
public void setNextVerify(Verify verify){
this.verify = verify;
}
public abstract void deploy(UserAccount userAccount);
}
3. 校验器子类
-
用户名校验器
javapublic class UsernameVerify extends Verify { @Override public void deploy(UserAccount userAccount) { if (StringUtils.isNotEmpty(userAccount.getUsername()) && userAccount.getUsername().length() <= 20) { System.out.println("用户名验证通过"); // 获取下一个验证器 if (verify != null) { verify.deploy(userAccount); } } else { System.out.println("用户名不能为空,长度不能超过20个字符!"); } } }
-
密码校验器
javapublic class PasswordVerify extends Verify { @Override public void deploy(UserAccount userAccount) { if (checkPassword(userAccount.getPassword())) { System.out.println("密码校验通过"); // 获取下一个验证器 if (verify != null) { verify.deploy(userAccount); } } else { System.out.println("密码不能为空,长度不能超过16个字符,必须包含数字和字母"); } } public boolean checkPassword(String password) { if (StringUtils.isEmpty(password)) { return false; } if (password.length() > 16) { return false; } return password.matches(".*[0-9].*") && password.matches(".*[a-zA-Z].*"); } }
-
电话号码校验器
javapublic class PhoneNumberVerify extends Verify { @Override public void deploy(UserAccount userAccount) { if (checkPhoneNumber(userAccount.getPhoneNumber())) { System.out.println("手机号码验证通过"); if (verify != null) { verify.deploy(userAccount); } } else { System.out.println("校验手机号,手机号不能为空,必须是11位数字"); } } public boolean checkPhoneNumber(String phoneNumber) { if (StringUtils.isEmpty(phoneNumber)) { return false; } if (phoneNumber.length() != 11) { return false; } for (int i = 0; i < 11; i++) { if (!Character.isDigit(phoneNumber.charAt(i))) { return false; } } return true; } }
4. 客户端测试
java
public class Client {
public static void main(String[] args) {
UsernameVerify usernameVerify = new UsernameVerify();
PasswordVerify passwordVerify = new PasswordVerify();
PhoneNumberVerify phoneNumberVerify = new PhoneNumberVerify();
UserAccount userAccount = new UserAccount();
userAccount.setUsername("qinj");
userAccount.setPassword("123456abc");
userAccount.setPhoneNumber("13800138000");
// 创建职责链
usernameVerify.setNextVerify(passwordVerify);
passwordVerify.setNextVerify(phoneNumberVerify);
// 开始验证
usernameVerify.deploy(userAccount);
}
}
5. 运行结果
五、责任链模式的应用场景
5.1 适用场景
- 多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动决定,可以使用责任链模式。
- 可由客户端动态指定一组对象处理请求,或添加新的处理者,可以使用责任链模式。
- 所需处理者及其顺序必须在运行时进行改变,可以使用责任链模式。
5.2 应用实例
- 用户认证和授权
- 日志记录和异常处理
- 生活中各种审批链(请假、采购)
- 各种客服的专线(人工客服按指定数字转接)
JS
中的事件冒泡Tomcat 对
Encoding` 的处理Java
的拦截器链Servlet
的过滤器链