策略模式

用策略模式消除 if-else,实现算法的灵活切换

问题

什么是策略模式?如何在前端中应用策略模式?

解答

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

基本结构

// 策略对象:封装具体算法
const strategies = {
  strategyA(params) {
    // 算法 A
  },
  strategyB(params) {
    // 算法 B
  }
};

// 上下文:使用策略
function context(strategyName, params) {
  return strategies[strategyName](params);
}

示例:表单验证

// 验证策略
const validators = {
  required(value, message) {
    return value.trim() !== '' ? null : message;
  },
  
  minLength(value, message, min) {
    return value.length >= min ? null : message;
  },
  
  maxLength(value, message, max) {
    return value.length <= max ? null : message;
  },
  
  email(value, message) {
    const reg = /^[\w.-]+@[\w.-]+\.\w+$/;
    return reg.test(value) ? null : message;
  },
  
  phone(value, message) {
    const reg = /^1[3-9]\d{9}$/;
    return reg.test(value) ? null : message;
  }
};

// 验证器
class Validator {
  constructor() {
    this.rules = [];
  }
  
  // 添加规则
  add(value, rules) {
    rules.forEach(rule => {
      this.rules.push(() => {
        const { strategy, message, ...params } = rule;
        const args = [value, message, ...Object.values(params)];
        return validators[strategy](...args);
      });
    });
  }
  
  // 执行验证
  validate() {
    for (const rule of this.rules) {
      const error = rule();
      if (error) return error;
    }
    return null;
  }
}

// 使用
const validator = new Validator();

validator.add('', [
  { strategy: 'required', message: '用户名不能为空' },
  { strategy: 'minLength', message: '用户名至少 3 个字符', min: 3 }
]);

validator.add('test@', [
  { strategy: 'email', message: '邮箱格式不正确' }
]);

const error = validator.validate();
console.log(error); // "用户名不能为空"

示例:价格计算

// 价格策略
const priceStrategies = {
  normal(price) {
    return price;
  },
  
  member(price) {
    return price * 0.9; // 9 折
  },
  
  vip(price) {
    return price * 0.8; // 8 折
  },
  
  promotion(price, discount) {
    return price * discount;
  }
};

// 计算价格
function calculatePrice(type, price, ...args) {
  const strategy = priceStrategies[type];
  if (!strategy) {
    throw new Error(`未知的价格策略: ${type}`);
  }
  return strategy(price, ...args);
}

// 使用
console.log(calculatePrice('normal', 100));      // 100
console.log(calculatePrice('member', 100));      // 90
console.log(calculatePrice('vip', 100));         // 80
console.log(calculatePrice('promotion', 100, 0.7)); // 70

示例:动画缓动

// 缓动策略
const easings = {
  linear(t) {
    return t;
  },
  
  easeIn(t) {
    return t * t;
  },
  
  easeOut(t) {
    return t * (2 - t);
  },
  
  easeInOut(t) {
    return t < 0.5 
      ? 2 * t * t 
      : -1 + (4 - 2 * t) * t;
  }
};

// 动画函数
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 easedProgress = easingFn(progress);
    const value = from + (to - from) * easedProgress;
    
    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
function calculatePrice(type, price) {
  if (type === 'normal') {
    return price;
  } else if (type === 'member') {
    return price * 0.9;
  } else if (type === 'vip') {
    return price * 0.8;
  }
  // 新增类型需要修改这个函数
}

// 用策略模式 - 开放扩展,关闭修改
const priceStrategies = {
  normal: price => price,
  member: price => price * 0.9,
  vip: price => price * 0.8
};

// 新增策略只需添加属性
priceStrategies.superVip = price => price * 0.7;

关键点

  • 策略模式将算法封装成独立对象,通过组合替代继承
  • 消除条件分支,用策略对象的多态性替代 if-else
  • 符合开闭原则:新增策略无需修改现有代码
  • 前端常见场景:表单验证、价格计算、动画缓动、权限控制
  • JavaScript 中函数是一等公民,策略可以直接用函数实现,无需类