责任链模式

在讨论责任链模式之前,先来看前端经常需要实现的表单验证功能: 假如注册新用户要验证用户名,密码,手机号:

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 优雅。

缺点

  • 责任链太长,调试可能比较麻烦。
  • 责任链太长或者处理者处理速度慢,可能会影响系统的性能。

使用场景

有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。比如,事件处理、请求拦截、中间件等。

相关推荐
GDAL5 分钟前
vue3入门教程:ref能否完全替代reactive?
前端·javascript·vue.js
六卿5 分钟前
react防止页面崩溃
前端·react.js·前端框架
z千鑫31 分钟前
【前端】详解前端三大主流框架:React、Vue与Angular的比较与选择
前端·vue.js·react.js
m0_748256141 小时前
前端 MYTED单篇TED词汇学习功能优化
前端·学习
小白学前端6662 小时前
React Router 深入指南:从入门到进阶
前端·react.js·react
web130933203982 小时前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端
outstanding木槿3 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
好名字08213 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
隐形喷火龙3 小时前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_748241124 小时前
Selenium之Web元素定位
前端·selenium·测试工具