【设计模式】策略模式:告别嵌套 if-else,让代码像搭积木一样优雅

【设计模式】策略模式:告别嵌套 if-else,让代码像搭积木一样优雅 摘要在 JavaScript 开发中面对复杂的业务分支如多种登录方式、不同促销算法、表单验证规则我们常常陷入if-else或switch-case的泥潭。随着业务迭代这些代码变得难以维护且脆弱。本文结合 ES6 新特性深入解析如何利用**策略模式Strategy Pattern**重构代码利用对象映射和多态思想打造高可扩展、易测试的前端/Node.js 架构。一、痛点当if-else统治了世界在日常开发中你是否见过这样的代码// ❌ 糟糕的写法随着业务增加这个函数会越来越长functioncalculateDiscount(userType,amount){letdiscount0;if(userTypeVIP){// VIP 打 8 折discountamount*0.2;console.log(应用 VIP 折扣);}elseif(userTypeSVIP){// SVIP 打 5 折且满 100 减 20discountamount*0.5;if(amount100){discount20;}console.log(应用 SVIP 折扣);}elseif(userTypeNEW_USER){// 新用户首单立减 10 元discount10;console.log(应用新人优惠);}elseif(userTypeDOUBLE_11){// 双 11 特殊逻辑...// 代码越来越长每次加新活动都要改这里容易误伤旧逻辑discountamount*0.3;}else{// 普通用户无折扣discount0;}returnamount-discount;}存在的问题违反开闭原则OCP新增一种用户类型必须修改原函数风险极高。逻辑耦合所有算法逻辑挤在一个函数里难以单独测试某个策略。可读性差核心计算逻辑被大量的条件判断掩盖。难以复用如果其他地方也需要“VIP 折扣逻辑”只能复制粘贴代码。二、破局策略模式在 JS 中的两种实现策略模式的核心定义一系列算法把它们一个个封装起来并且使它们可相互替换。在 JavaScript 中由于函数是一等公民First-class Citizen我们不需要像 Java 那样必须定义接口和抽象类实现方式更加灵活轻量。方案一对象字面量映射推荐轻量级首选利用 JS 的对象特性将策略定义为键值对Key 是策略标识Value 是具体的执行函数。// ✅ 重构后策略对象constdiscountStrategies{VIP:(amount){console.log(【策略】应用 VIP 折扣);returnamount*0.2;},SVIP:(amount){console.log(【策略】应用 SVIP 折扣);letdiscountamount*0.5;if(amount100)discount20;returndiscount;},NEW_USER:(){console.log(【策略】应用新人优惠);return10;},DOUBLE_11:(amount){console.log(【策略】应用双 11 特惠);returnamount*0.3;},// 默认策略DEFAULT:()0};// 上下文函数负责调用策略functioncalculateDiscount(userType,amount){// 获取策略如果没有则使用默认策略conststrategydiscountStrategies[userType]||discountStrategies.DEFAULT;// 执行策略constdiscountstrategy(amount);returnamount-discount;}// 调用console.log(calculateDiscount(VIP,200));// 输出: 160console.log(calculateDiscount(UNKNOWN,200));// 输出: 200 (走默认)优点极简无需类结构代码量少。直观策略注册表一目了然。易扩展新增策略只需在对象中添加一个属性无需修改调用逻辑。方案二类与多态适用于复杂场景如果策略内部状态复杂或者需要继承公共方法可以使用 ES6 Class。// 1. 定义策略接口在 JS 中通常通过约定或抽象类体现classDiscountStrategy{calculate(amount){thrownewError(子类必须实现 calculate 方法);}}// 2. 具体策略实现classVipStrategyextendsDiscountStrategy{calculate(amount){console.log(【类策略】VIP 计算中...);returnamount*0.2;}}classSvipStrategyextendsDiscountStrategy{calculate(amount){console.log(【类策略】SVIP 计算中...);returnamount*0.5(amount100?20:0);}}// 3. 策略工厂/上下文classDiscountContext{constructor(){this.strategyMapnewMap([[VIP,newVipStrategy()],[SVIP,newSvipStrategy()],// 可以在运行时动态注册新策略]);}execute(type,amount){conststrategythis.strategyMap.get(type);if(!strategy){console.warn(未找到策略${type}使用默认逻辑);returnamount;}returnamount-strategy.calculate(amount);}// 动态注册新策略符合开闭原则registerStrategy(type,strategyInstance){this.strategyMap.set(type,strategyInstance);}}// 使用constcontextnewDiscountContext();console.log(context.execute(VIP,200));三、实战场景表单验证器的策略化除了价格计算策略模式在表单验证中也是神器。// 定义验证策略库constvalidators{required:(value)value.trim()!?null:此项不能为空,email:(value)/^[^\s][^\s]\.[^\s]$/.test(value)?null:邮箱格式不正确,minLength:(min)(value)value.lengthmin?null:长度不能少于${min},phone:(value)/^1[3-9]\d{9}$/.test(value)?null:手机号格式错误};// 验证执行器functionvalidateField(value,rules){for(letruleofrules){// 处理带参数的规则 (如 minLength: 6)letstrategyFn;leterrorMsg;if(typeofrulestring){// 简单规则requiredstrategyFnvalidators[rule];}elseif(typeofruleobject){// 带参规则{ type: minLength, value: 6 }const{type,value:param}rule;strategyFnvalidators[type](param);}errorMsgstrategyFn(value);if(errorMsg)returnerrorMsg;// 一旦失败立即返回}returnnull;// 验证通过}// 使用示例constusernameErrorvalidateField(,[required,{type:minLength,value:3}]);constemailErrorvalidateField(abc,[required,email]);console.log(usernameError);// 此项不能为空console.log(emailError);// 邮箱格式不正确优势配置化验证规则可以变成 JSON 配置从后端下发前端无需改代码。组合灵活可以随时组合不同的验证规则。四、进阶技巧结合 ES2025 新特性在 2025 年的 JS 环境中我们可以利用新特性让策略模式更强大私有字段 (#)在策略类中保护内部状态。模块化导入利用动态import()实现策略的懒加载对于大型策略库非常有用。// 懒加载策略示例asyncfunctiongetStrategy(type){try{// 只有当用户选择该策略时才加载对应的文件constmoduleawaitimport(./strategies/${type}.js);returnmodule.default;}catch(e){returndefaultStrategy;}}五、总结与温习笔记什么时候该用策略模式多重条件判断代码中出现大量的if-else或switch区分不同行为。算法隔离同一个接口的多种实现希望它们互不干扰独立测试。动态切换需要在运行时根据配置或用户输入改变算法。避免污染不想让主类膨胀希望将复杂逻辑拆分到独立文件或模块中。核心口诀JS 版对象映射替 switch函数作为策略体。新增只需加属性原有逻辑不动地。验证计算皆可用代码清爽笑嘻嘻。注意事项策略过多如果策略非常多几十个可以考虑配合工厂模式或动态导入来管理。参数一致性确保所有策略函数接收的参数结构一致否则调用时会报错。默认兜底永远要有一个default策略防止未知的类型导致程序崩溃。日常记录下次在写if (type A) ... else if (type B)时停下手里的键盘问自己“能不能把它变成一个对象的一个属性”往往这一步思考就能让代码质量提升一个档次。#JavaScript #设计模式 #策略模式 #前端架构 #代码重构 #ES6 #消除ifelse