Strategy Pattern

策略模式的实现与应用场景

问题

什么是策略模式?如何在 JavaScript 中实现策略模式?

解答

策略模式将一组算法封装成独立的策略对象,使它们可以相互替换,让算法的变化独立于使用它的客户端。

基本实现

// 定义策略对象
const strategies = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b,
};

// 上下文:使用策略
function calculate(strategy, a, b) {
  return strategies[strategy](a, b);
}

// 使用
console.log(calculate('add', 10, 5));      // 15
console.log(calculate('multiply', 10, 5)); // 50

表单验证示例

// 验证策略
const validators = {
  required: (value) => ({
    valid: value !== '',
    message: '此字段必填',
  }),

  minLength: (min) => (value) => ({
    valid: value.length >= min,
    message: `最少 ${min} 个字符`,
  }),

  email: (value) => ({
    valid: /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(value),
    message: '邮箱格式不正确',
  }),

  phone: (value) => ({
    valid: /^1[3-9]\d{9}$/.test(value),
    message: '手机号格式不正确',
  }),
};

// 验证器
class Validator {
  constructor() {
    this.rules = [];
  }

  // 添加验证规则
  addRule(validator) {
    this.rules.push(validator);
    return this; // 支持链式调用
  }

  // 执行验证
  validate(value) {
    for (const rule of this.rules) {
      const result = rule(value);
      if (!result.valid) {
        return result;
      }
    }
    return { valid: true, message: '' };
  }
}

// 使用
const emailValidator = new Validator()
  .addRule(validators.required)
  .addRule(validators.email);

console.log(emailValidator.validate(''));           // { valid: false, message: '此字段必填' }
console.log(emailValidator.validate('abc'));        // { valid: false, message: '邮箱格式不正确' }
console.log(emailValidator.validate('a@b.com'));    // { valid: true, message: '' }

价格计算示例

// 折扣策略
const discountStrategies = {
  normal: (price) => price,
  vip: (price) => price * 0.9,
  svip: (price) => price * 0.8,
  coupon: (discount) => (price) => price - discount,
  percent: (percent) => (price) => price * (1 - percent / 100),
};

// 价格计算器
class PriceCalculator {
  constructor(strategy = discountStrategies.normal) {
    this.strategy = strategy;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
  }

  calculate(price) {
    return this.strategy(price);
  }
}

// 使用
const calculator = new PriceCalculator();

// 普通用户
console.log(calculator.calculate(100)); // 100

// VIP 用户
calculator.setStrategy(discountStrategies.vip);
console.log(calculator.calculate(100)); // 90

// 使用优惠券
calculator.setStrategy(discountStrategies.coupon(15));
console.log(calculator.calculate(100)); // 85

动画缓动函数

// 缓动策略
const easings = {
  linear: (t) => t,
  easeIn: (t) => t * t,
  easeOut: (t) => t * (2 - t),
  easeInOut: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
  bounce: (t) => {
    if (t < 1 / 2.75) return 7.5625 * t * t;
    if (t < 2 / 2.75) return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
    if (t < 2.5 / 2.75) return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
    return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
  },
};

// 动画函数
function animate({ from, to, duration, easing = 'linear', onUpdate }) {
  const start = performance.now();
  const easingFn = easings[easing];

  function tick(now) {
    const elapsed = now - start;
    const progress = Math.min(elapsed / duration, 1);
    const value = from + (to - from) * easingFn(progress);

    onUpdate(value);

    if (progress < 1) {
      requestAnimationFrame(tick);
    }
  }

  requestAnimationFrame(tick);
}

// 使用
animate({
  from: 0,
  to: 300,
  duration: 1000,
  easing: 'easeOut',
  onUpdate: (value) => {
    element.style.transform = `translateX(${value}px)`;
  },
});

关键点

  • 消除条件分支:用策略对象替代 if-else 或 switch-case
  • 开闭原则:新增策略无需修改现有代码,只需添加新策略
  • 策略可复用:同一策略可在不同上下文中使用
  • 运行时切换:可以动态更换算法,无需修改客户端代码
  • 适用场景:表单验证、价格计算、动画缓动、权限控制等多算法选择场景