职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。职责链模式的名字非常形象,一系列可能会处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点
我负责品牌广告词包的录入,我需要对用户在标签输入框的输入进行处理,词包有很多种类,校验规则也不同:
- 品牌关键词:长度不能超过8个字符,不能重复,不能包含符号
- 长尾关键词:长度在8-30个字符之间,不能重复,不能包含符号
- 问答性关键词:长度在8-30个字符之间,不能重复,结尾可以是英文问号,不能包含其他符号
如果使用普通函数和大量的if-else
语句来实现的校验逻辑:
import { v4 as uuidv4 } from 'uuid';
interface ValidationResult {
id: string;
name: string;
error?: string;
}
function validateKeywordLength(keyword: string, type: 'brand' | 'longTail' | 'qnA'): string | null {
if (type === 'brand' && (keyword.length <= 0 || keyword.length > 8)) {
return `Length should be between 1 and 8 characters`;
}
if ((type === 'longTail' || type === 'qnA') && (keyword.length < 8 || keyword.length > 30)) {
return `Length should be between 8 and 30 characters`;
}
return null;
}
function checkDuplicate(keyword: string, keywords: string[]): string | null {
const occurrences = keywords.filter(item => item === keyword).length;
if (occurrences > 1) {
return 'Keyword is duplicated';
}
return null;
}
function validateSymbols(keyword: string, type: 'brand' | 'longTail' | 'qnA'): string | null {
const hasSymbol = /[!@#$%^&*(),.:{}|<>]/.test(keyword);
if (type !== 'qnA' && hasSymbol) {
return 'Contains illegal symbols';
}
if (type === 'qnA') {
if (keyword.endsWith('?')) {
if (keyword.indexOf('?') !== keyword.lastIndexOf('?')) {
return 'Multiple question marks found';
}
} else if (hasSymbol) {
return 'Contains illegal symbols';
}
}
return null;
}
function validateKeyword(keyword: string, type: 'brand' | 'longTail' | 'qnA', keywords: string[]): ValidationResult {
const result: ValidationResult = {
id: uuidv4(),
name: keyword,
};
let error = validateKeywordLength(keyword, type);
if (!error) {
error = checkDuplicate(keyword, keywords);
}
if (!error) {
error = validateSymbols(keyword, type);
}
if (error) {
result.error = error;
}
return result;
}
function analyzeKeywords(input: string, type: 'brand' | 'longTail' | 'qnA'): ValidationResult[] {
const keywords = input.split(',');
const results: ValidationResult[] = keywords.map(keyword => validateKeyword(keyword, type, keywords));
return results;
}
// 使用示例
const input = "apple,orange,grape?,cherry!!,apple";
const results = analyzeKeywords(input, 'qnA');
console.log(results);
这种方法使用了大量的if-else
语句,并且逻辑较为直接,但可扩展性较差,并且难于维护。当需要添加新的校验规则或修改现有的校验逻辑时,可能需要修改多处代码。
使用责任链模式:
下面是代码的详细注释:
// 引入 uuid 库中的 v4 函数,为了生成唯一ID
//import { v4 as uuidv4 } from 'uuid';
// 定义校验结果的接口
interface ValidationResult {
id: string; // 唯一ID
name: string; // 输入的关键词
error?: string; // 可选的错误消息
}
// 抽象的校验器类,定义了责任链模式的基本结构
abstract class ValidatorChain {
nextValidator?: ValidatorChain; // 指向下一个校验器的引用
// 设置下一个校验器并返回它
setNext(validator: ValidatorChain): ValidatorChain {
this.nextValidator = validator;
return validator;
}
// 默认的校验方法,如果有下一个校验器则调用,否则返回基础结果
validate(item: string, array: string[]): ValidationResult {
return this.nextValidator?.validate(item, array) || { id: uuidv4(), name: item };
}
}
// 长度校验器
class LengthValidator extends ValidatorChain {
maxLength: number; // 允许的最大长度
minLength: number; // 允许的最小长度
constructor(minLength: number, maxLength: number) {
super(); // 调用父类的构造函数
this.minLength = minLength;
this.maxLength = maxLength;
}
// 对输入项进行长度校验
validate(item: string, array: string[]): ValidationResult {
if (item.length < this.minLength || item.length > this.maxLength) {
return {
id: uuidv4(),
name: item,
error: `Length should be between ${this.minLength} and ${this.maxLength} characters`
};
}
// 如果长度符合要求,继续下一个校验
return super.validate(item, array);
}
}
// 重复校验器
class DuplicateValidator extends ValidatorChain {
validate(item: string, array: string[]): ValidationResult {
// 判断是否有重复
const isDuplicate = array.indexOf(item) !== array.lastIndexOf(item);
if (isDuplicate) {
return {
//id: uuidv4(),
name: item,
error: 'Keyword is duplicated'
};
}
// 如果没有重复,继续下一个校验
return super.validate(item, array);
}
}
// 符号校验器
class SymbolValidator extends ValidatorChain {
validate(item: string, array: string[]): ValidationResult {
// 检查是否含有不允许的符号
const hasSymbol = /[!@#$%^&*(),.:{}|<>]/.test(item);
if (hasSymbol) {
return {
//id: uuidv4(),
name: item,
error: 'Contains illegal symbols'
};
}
// 如果没有含有禁止的符号,继续下一个校验
return super.validate(item, array);
}
}
// 问答性关键词的符号校验器(对 ? 的处理有所不同)
class QnASymbolValidator extends SymbolValidator {
validate(item: string, array: string[]): ValidationResult {
// 如果关键词以 ? 结尾,则继续下一个校验
if (item.endsWith('?')) {
return super.validate(item, array);
}
// 检查是否含有除 ? 以外的不允许的符号
const hasOtherSymbols = /[^?][!@#$%^&*(),.:{}|<>]/.test(item);
if (hasOtherSymbols) {
return {
//id: uuidv4(),
name: item,
error: 'Contains illegal symbols or multiple question marks'
};
}
// 如果符号符合要求,继续下一个校验
return super.validate(item, array);
}
}
// 创建针对各种关键词类型的责任链
const brandKeywordValidator = new LengthValidator(0, 8)
.setNext(new DuplicateValidator())
.setNext(new SymbolValidator());
const longTailKeywordValidator = new LengthValidator(8, 30)
.setNext(new DuplicateValidator())
.setNext(new SymbolValidator());
const qnAKeywordValidator = new LengthValidator(8, 30)
.setNext(new DuplicateValidator())
.setNext(new QnASymbolValidator());
// 根据用户输入的关键词类型来执行相应的责任链校验
function analyzeKeywords(input: string, type: 'brand' | 'longTail' | 'qnA'): ValidationResult[] {
const keywords = input.split(','); // 将输入按逗号分割
let validator: ValidatorChain;
if (type === 'brand') {
validator = brandKeywordValidator;
} else if (type === 'longTail') {
validator = longTailKeywordValidator;
} else if (type === 'qnA') {
validator = qnAKeywordValidator;
} else {
throw new Error('Invalid keyword type'); // 如果类型不匹配,抛出错误
}
// 对每个关键词执行责任链校验
const results: ValidationResult[] = keywords.map((keyword) => validator.validate(keyword, keywords));
return results;
}
// 使用示例
const input = "apple,orange,grape?,cherry!!";
const results = analyzeKeywords(input, 'qnA');
console.log(results); // 输出校验结果
好的,这里是带有详细注释的完整代码:
import { v4 as uuidv4 } from 'uuid';
// 定义校验结果的接口
interface ValidationResult {
id: string;
name: string;
error?: string; // 用于存储错误信息的字段
}
// 定义基础的校验器抽象类
abstract class ValidatorChain {
nextValidator?: ValidatorChain;
// 设置下一个校验器
setNext(validator: ValidatorChain): ValidatorChain {
this.nextValidator = validator;
return validator;
}
// 校验方法,子类需要根据具体的校验逻辑进行重写
validate(item: string, type: 'brand' | 'longTail' | 'qnA', array: string[]): ValidationResult {
return this.nextValidator?.validate(item, type, array) || { id: uuidv4(), name: item };
}
}
// 定义长度校验器
class LengthValidator extends ValidatorChain {
maxLength: number;
minLength: number;
applicableTypes: ('brand' | 'longTail' | 'qnA')[];
// 接收最小长度、最大长度和适用的关键词类型作为参数
constructor(minLength: number, maxLength: number, applicableTypes: ('brand' | 'longTail' | 'qnA')[]) {
super();
this.minLength = minLength;
this.maxLength = maxLength;
this.applicableTypes = applicableTypes;
}
validate(item: string, type: 'brand' | 'longTail' | 'qnA', array: string[]): ValidationResult {
// 判断当前关键词类型是否适用于此校验器
if (!this.applicableTypes.includes(type)) {
return super.validate(item, type, array);
}
if (item.length < this.minLength || item.length > this.maxLength) {
return {
id: uuidv4(),
name: item,
error: `Length should be between ${this.minLength} and ${this.maxLength} characters`
};
}
return super.validate(item, type, array);
}
}
// 定义重复校验器
class DuplicateValidator extends ValidatorChain {
applicableTypes: ('brand' | 'longTail' | 'qnA')[];
constructor(applicableTypes: ('brand' | 'longTail' | 'qnA')[]) {
super();
this.applicableTypes = applicableTypes;
}
validate(item: string, type: 'brand' | 'longTail' | 'qnA', array: string[]): ValidationResult {
if (!this.applicableTypes.includes(type)) {
return super.validate(item, type, array);
}
const isDuplicate = array.indexOf(item) !== array.lastIndexOf(item);
if (isDuplicate) {
return {
id: uuidv4(),
name: item,
error: 'Keyword is duplicated'
};
}
return super.validate(item, type, array);
}
}
// 定义符号校验器
class SymbolValidator extends ValidatorChain {
applicableTypes: ('brand' | 'longTail' | 'qnA')[];
constructor(applicableTypes: ('brand' | 'longTail' | 'qnA')[]) {
super();
this.applicableTypes = applicableTypes;
}
validate(item: string, type: 'brand' | 'longTail' | 'qnA', array: string[]): ValidationResult {
if (!this.applicableTypes.includes(type)) {
return super.validate(item, type, array);
}
const hasSymbol = /[!@#$%^&*(),.:{}|<>]/.test(item);
if (hasSymbol) {
return {
id: uuidv4(),
name: item,
error: 'Contains illegal symbols'
};
}
return super.validate(item, type, array);
}
}
// 定义问答关键词的符号校验器,继承自SymbolValidator
class QnASymbolValidator extends SymbolValidator {
validate(item: string, type: 'brand' | 'longTail' | 'qnA', array: string[]): ValidationResult {
if (item.endsWith('?')) {
return super.validate(item, type, array);
}
const hasOtherSymbols = /[^?][!@#$%^&*(),.:{}|<>]/.test(item);
if (hasOtherSymbols) {
return {
id: uuidv4(),
name: item,
error: 'Contains illegal symbols or multiple question marks'
};
}
return super.validate(item, type, array);
}
}
// 创建一个通用的责任链
const keywordValidator = new LengthValidator(0, 8, ['brand'])
.setNext(new LengthValidator(8, 30, ['longTail', 'qnA']))
.setNext(new DuplicateValidator(['brand', 'longTail', 'qnA']))
.setNext(new SymbolValidator(['brand', 'longTail']))
.setNext(new QnASymbolValidator(['qnA']));
// 根据关键词类型执行单一责任链的校验
function analyzeKeywords(input: string, type: 'brand' | 'longTail' | 'qnA'): ValidationResult[] {
const keywords = input.split(',');
const results: ValidationResult[] = keywords.map((keyword) => keywordValidator.validate(keyword, type, keywords));
return results;
}
// 使用示例
const input = "apple,orange,grape?,cherry!!";
const results = analyzeKeywords(input, 'qnA');
console.log(results);
这段代码展示了如何创建一个通用的责任链,根据关键词的类型决定是否执行校验。通过对校验器的适用关键词类型进行配置,可以轻松地对责任链进行扩展和修改。
"哪种方式更好"取决于具体的应用场景和需求。下面我们对两种方式进行比较:
三条责任链的优势:
- 明确性:每条链有明确的责任和用途,对于开发者而言,更容易理解每条链的目的。
- 独立性:各自的链可以独立地进行修改和扩展,不会对其他链产生影响。
- 可读性:当一个特定类型的关键词需要经过的验证规则较多时,将它们组织在单独的责任链中可能更有可读性。
单条责任链的优势:
- 灵活性:在一个统一的链中,可以轻松地为各种关键词类型配置和修改验证规则。
- 维护:如果多种关键词类型有共同的验证规则,那么只需在单一的责任链中进行修改,而不需要在每条链上进行重复的修改。
- 扩展性:增加新的关键词类型或新的验证规则时,只需修改一条链。
选择的建议:
- 如果每种关键词类型的验证规则都相对独立,并且未来不太可能共享验证逻辑,那么采用三条责任链可能更有优势。
- 如果多种关键词类型有很多共同的验证规则,并且希望能够轻松地为各种类型的关键词配置验证规则,那么单条责任链可能更合适。
- 如果考虑到未来的扩展性,或者验证规则会频繁变动,单条责任链的方式可能更为灵活和易于维护。