在讨论责任链模式之前,先来看前端经常需要实现的表单验证功能: 假如注册新用户要验证用户名,密码,手机号:
ts
interface FormData {
name?: string;
password?: string;
phone?: string;
}
const validateFormData = (formData: FormData) => {
const { name, password, phone } = formData;
if (name && name.length >= 3 && name.length <= 10) {
if (password && password.length >= 6 && name.length <= 12) {
if (phone && phone.length === 11) {
return true;
}
}
}
return false;
};
显然,这个代码并不那么友好,当要修改验证规则时,不得不深入代码修改,不符合开闭原则。我们将代码进行优化:
ts
const validateNamePhone = (data: Data) => {
const { phone } = data;
if (phone && phone.length === 11) {
return true;
}
return false;
};
const validateNamePassword = (data: Data) => {
const { password } = data;
if (password && password.length >= 6 && password.length <= 12) {
return validateNamePhone(data);
}
return false;
};
const validateName = (data: Data) => {
const { name } = data;
if (name && name.length >= 3 && name.length <= 10) {
return validateNamePassword(data);
}
return false;
};
console.log(validateName({ name: "te" })); //false
console.log(validateName({ name: "test", password: "pa" })); //false
console.log(
validateName({ name: "test", password: "abcdedf", phone: "13141234125" })
); //true
可以看到代码得到了优化,之前大的 if-else 被拆分成了几个小的函数,结构更加清晰,但函数里的依赖链条比较死板,如果中间增加校验步骤,就不得不打碎这个链条,深入代码进行修改。
责任链
前述的代码已经有责任链的雏形了,那么什么是责任链? 责任链模式的定义就是当请求到来时,让中间所有对象都能够有机会处理请求,从而避免了请求的发送者与后续的一些列接收者之间的耦合关系。
通常来讲,在职责链模式里,中间的对象由每一个对象对其下家的引用而连接起来形成一条链,请求在这个链上传递,链上对象都可以处理此请求。我们常举的一个例子就是,公交车上传递乘车费,乘车人只需要将钱传递给第一个人,然后由他继续传递,最后到售票员。在这个过程中乘车人并不需要知道除第一个人以外的其他的人,不需要知道给他传递钱的是男是女,售票员的高矮胖瘦,这样乘车人与后续一些列传递人的关系就解耦了。
下图是普通方式请求处理和责任链模式处理请求的一个对比图:
责任链模式类图:
实现:
ts
abstract class Handler {
private next: Handler;
public getNext(): Handler {
return this.next;
}
public setNext(next: Handler) {
this.next = next;
}
public abstract handle(request: object): void;
}
class ConcreteHandler1 extends Handler {
public handle(request: object) {
console.log(`concrete handler1 excute request`);
const next = this.getNext();
if (next) {
next.handle(request);
}
}
}
class ConcreteHandler2 extends Handler {
public handle(request: object) {
console.log(`concrete handler2 excute request`);
const next = this.getNext();
if (next) {
next.handle(request);
}
}
}
class ConcreteHandler3 extends Handler {
public handle(request: object) {
console.log(`concrete handler3 excute request`);
const next = this.getNext();
if (next) {
next.handle(request);
}
}
}
const main = () => {
const handler1 = new ConcreteHandler1();
const handler2 = new ConcreteHandler2();
const handler3 = new ConcreteHandler3();
handler1.setNext(handler2);
handler2.setNext(handler3);
handler1.handle({ message: "test" });
};
main();
对于前述表单校验的例子,用责任链模式实现如下:
ts
abstract class Validator {
private next: Validator;
public getNext(): Validator {
return this.next;
}
public setNext(next: Validator) {
this.next = next;
}
public abstract validate(request: Data): void;
}
class NameValidator extends Validator {
public validate(request: Data) {
const { name } = request;
const isValid = name && name.length >= 3 && name.length <= 10;
if (isValid) {
const next = this.getNext();
if (next) {
return next.validate(request);
}
return true;
}
return false;
}
}
class PasswordValidator extends Validator {
public validate(request: Data) {
const { password } = request;
const isValid = password && password.length >= 6 && password.length <= 12;
if (isValid) {
const next = this.getNext();
if (next) {
return next.validate(request);
}
return true;
}
return false;
}
}
class PhoneValidator extends Validator {
public validate(request: Data) {
const { phone } = request;
const isValid = phone && phone.length === 11;
if (isValid) {
const next = this.getNext();
if (next) {
return next.validate(request);
}
return true;
}
return false;
}
}
//客户端程序
const main = () => {
const nameValidator = new NameValidator();
const passwordValidator = new PasswordValidator();
const phoneValidator = new PhoneValidator();
nameValidator.setNext(passwordValidator);
passwordValidator.setNext(phoneValidator);
console.log(nameValidator.validate({ name: "test" })); //false
console.log(nameValidator.validate({ name: "test", password: "pa" })); //false
console.log(
nameValidator.validate({
name: "test",
password: "abcdedf",
phone: "13141234125",
})
); //true
};
main();
可以看到通过责任链实现校验后,更加灵活了。假如要添加身份证校验的功能,只需要实现 IdValidator,不需要侵入原来的代码,在客户端只需要重新添加处理节点就可以了。
在上述的例子中,责任链当前节点处理完成之后,自己将请求传递给下一个处理节点处理,事实上请求的传递也可以统一由请求管理对象统一传递,只要符合责任链的核心概念,都可以叫责任链模式。
实现大致如下:
ts
abstract class Validator {
private next: Validator;
public getNext(): Validator {
return this.next;
}
public setNext(next: Validator) {
this.next = next;
}
public abstract validate(request: Data): boolean;
}
class NameValidator extends Validator {
public validate(request: Data) {
const { name } = request;
const isValid = name && name.length >= 3 && name.length <= 10;
return isValid;
}
}
class PasswordValidator extends Validator {
public validate(request: Data) {
const { password } = request;
const isValid = password && password.length >= 6 && password.length <= 12;
return isValid;
}
}
class PhoneValidator extends Validator {
public validate(request: Data) {
const { phone } = request;
const isValid = phone && phone.length === 11;
return isValid;
}
}
class Chain {
nodes: Validator[] = [];
constructor(nodes: Validator[]) {
this.nodes = nodes;
}
validate(request: Data) {
for (let i = 0; i < this.nodes.length; i += 1) {
const node = this.nodes[i];
const isValid = node.validate(request);
if (!isValid) {
return false;
}
}
return true;
}
}
const main = () => {
const nameValidator = new NameValidator();
const passwordValidator = new PasswordValidator();
const phoneValidator = new PhoneValidator();
const chain = new Chain([nameValidator, passwordValidator, phoneValidator]);
console.log(chain.validate({ name: "test" }));
console.log(chain.validate({ name: "test", password: "pa" }));
console.log(
chain.validate({ name: "test", password: "abcdedf", phone: "13141234125" })
);
};
总结
优点
-
解耦
请求的发送者最多只需要知道第一个处理者,而不用关心请求后续被谁以及如何处理。
-
灵活
当新增处理者时,只需要实现处理逻辑,然后插入到对应的位置就可以了,客户端不会感知到处理器的变化。
-
符合开闭原则
比 if-else 优雅。
缺点
- 责任链太长,调试可能比较麻烦。
- 责任链太长或者处理者处理速度慢,可能会影响系统的性能。
使用场景
有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。比如,事件处理、请求拦截、中间件等。