责任链模式

职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。职责链模式的名字非常形象,一系列可能会处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点

我负责品牌广告词包的录入,我需要对用户在标签输入框的输入进行处理,词包有很多种类,校验规则也不同:

  • 品牌关键词:长度不能超过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);

这段代码展示了如何创建一个通用的责任链,根据关键词的类型决定是否执行校验。通过对校验器的适用关键词类型进行配置,可以轻松地对责任链进行扩展和修改。

"哪种方式更好"取决于具体的应用场景和需求。下面我们对两种方式进行比较:

三条责任链的优势

  1. 明确性:每条链有明确的责任和用途,对于开发者而言,更容易理解每条链的目的。
  2. 独立性:各自的链可以独立地进行修改和扩展,不会对其他链产生影响。
  3. 可读性:当一个特定类型的关键词需要经过的验证规则较多时,将它们组织在单独的责任链中可能更有可读性。

单条责任链的优势

  1. 灵活性:在一个统一的链中,可以轻松地为各种关键词类型配置和修改验证规则。
  2. 维护:如果多种关键词类型有共同的验证规则,那么只需在单一的责任链中进行修改,而不需要在每条链上进行重复的修改。
  3. 扩展性:增加新的关键词类型或新的验证规则时,只需修改一条链。

选择的建议

  • 如果每种关键词类型的验证规则都相对独立,并且未来不太可能共享验证逻辑,那么采用三条责任链可能更有优势。
  • 如果多种关键词类型有很多共同的验证规则,并且希望能够轻松地为各种类型的关键词配置验证规则,那么单条责任链可能更合适。
  • 如果考虑到未来的扩展性,或者验证规则会频繁变动,单条责任链的方式可能更为灵活和易于维护。

你可能感兴趣的:(责任链模式,前端)