☰
别再写一堆 if-else 了:用策略模式重构你的业务代码
2026/10/15 13:01:10 网站建设 项目流程
一、先来看一段"眼熟"的代码

做订单系统的时候,几乎每个人都写过类似这样的东西:

defcalc_discount(order,user_level):ifuser_level=="normal":returnorder.amount*0.95elifuser_level=="vip":returnorder.amount*0.85elifuser_level=="svip":returnorder.amount*0.7else:returnorder.amount

刚上线时一切正常。三个月后,运营加了"新人券"“满减”“会员日”“渠道专享价”,代码变成了这样:

ifuser_level=="normal"andhas_couponandis_member_day:...elif...

嵌套越来越深,改一处崩两处,测试用例越堆越多,最后没人敢动这个文件。

问题的本质不在于逻辑复杂,而在于"判断"和"计算"被绑死在了一起。每次新增规则,都必须修改同一个函数,违反了开闭原则(对扩展开放,对修改关闭)。

解法就是策略模式——它可能是所有设计模式里最容易落地、性价比最高的一个。

二、策略模式的核心思想

一句话:把每个分支抽成一个独立的类(或函数),让它们实现同一个接口,然后在运行时按需切换。

结构上只有三个角色:

角色作用
Strategy(策略接口)定义统一的执行方法
ConcreteStrategy(具体策略)每个分支一个实现
Context(上下文)持有策略引用,负责调用

听起来很学术,看代码就明白了。

三、Python 版本:用字典当注册表

Python 里甚至不需要搞一堆类,一等公民的函数加上字典就够了:

fromdataclassesimportdataclassfromtypingimportCallable,Dict@dataclassclassOrder:amount:floatchannel:str="app"# 1. 每个策略就是一个普通函数defnormal_discount(order:Order)->float:returnorder.amount*0.95defvip_discount(order:Order)->float:returnorder.amount*0.85defsvip_discount(order:Order)->float:returnorder.amount*0.70# 2. 用字典做策略注册表DISCOUNT_STRATEGIES:Dict[str,Callable[[Order],float]]={"normal":normal_discount,"vip":vip_discount,"svip":svip_discount,}DEFAULT_STRATEGY=normal_discount# 3. 上下文:只负责查表和调用,不关心计算细节defcalc_discount(order:Order,user_level:str)->float:strategy=DISCOUNT_STRATEGIES.get(user_level,DEFAULT_STRATEGY)returnstrategy(order)# 使用order=Order(amount=299.0)print(calc_discount(order,"vip"))# 254.15print(calc_discount(order,"unknown"))# 284.05,走默认策略

新增一个渠道折扣,只需要加一个函数并注册一行,calc_discount一个字都不用改:

defchannel_discount(order:Order)->float:returnorder.amount*(0.9iforder.channel=="miniapp"else0.95)DISCOUNT_STRATEGIES["channel"]=channel_discount

如果策略本身有状态(比如要查数据库、要读配置),换成类更合适:

fromabcimportABC,abstractmethodclassDiscountStrategy(ABC):@abstractmethoddefapply(self,order:Order)->float:...classCouponStrategy(DiscountStrategy):def__init__(self,coupon_service):self.coupon_service=coupon_service# 依赖注入进来defapply(self,order:Order)->float:coupon=self.coupon_service.get_best(order)returnorder.amount-coupon.value
四、Java 版本:标准写法

Java 这边是教科书形态,但同样不复杂:

importjava.math.BigDecimal;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;// 1. 策略接口publicinterfaceDiscountStrategy{BigDecimalapply(Orderorder);}// 2. 具体策略publicclassVipDiscountStrategyimplementsDiscountStrategy{@OverridepublicBigDecimalapply(Orderorder){returnorder.getAmount().multiply(newBigDecimal("0.85"));}}publicclassSvipDiscountStrategyimplementsDiscountStrategy{@OverridepublicBigDecimalapply(Orderorder){returnorder.getAmount().multiply(newBigDecimal("0.70"));}}// 3. 上下文 + 策略注册表(Spring 环境下可以更优雅,见下文)publicclassDiscountCalculator{privatestaticfinalMap<String,DiscountStrategy>REGISTRY=newConcurrentHashMap<>();privatestaticfinalDiscountStrategyDEFAULT=Order::getAmount;static{REGISTRY.put("vip",newVipDiscountStrategy());REGISTRY.put("svip",newSvipDiscountStrategy());}publicstaticBigDecimalcalc(Orderorder,Stringlevel){returnREGISTRY.getOrDefault(level,DEFAULT).apply(order);}}

如果项目用了 Spring,连static注册表都可以省掉,靠容器自动装配:

@ServicepublicclassDiscountCalculator{privatefinalMap<String,DiscountStrategy>registry;// 关键:Spring 会把所有 DiscountStrategy 的实现类注入进这个 Map// key 默认是 bean 名称,正好拿来当策略名publicDiscountCalculator(Map<String,DiscountStrategy>strategies){this.registry=newConcurrentHashMap<>(strategies);}publicBigDecimalcalc(Orderorder,Stringlevel){DiscountStrategystrategy=registry.getOrDefault(level,registry.get("default"));returnOptional.ofNullable(strategy).orElse(IdentityStrategy.INSTANCE).apply(order);}}@Component("vip")publicclassVipDiscountStrategyimplementsDiscountStrategy{...}@Component("svip")publicclassSvipDiscountStrategyimplementsDiscountStrategy{...}

到这里你会发现:新增规则 = 新增一个类 + 一个注解,原有代码零修改。这就是开闭原则最直观的体现。

五、几个容易踩的坑

1. 别为了模式而模式。如果分支只有两个,且半年不会变,if-else 完全没问题。抽象是有成本的,多出来的十几个文件会让新人找入口找半天。我的经验法则是:同一个判断维度超过 4 个分支,或者预计会持续增长,才值得抽策略。

2. 策略之间不要互相依赖。A 策略里调 B 策略,链路会迅速变成网状。如果确实需要组合,请明确引入"组合策略"(Composite),让它成为显式的一层,而不是在策略内部偷偷耦合。

3. 别忘了兜底。注册表查不到时直接抛异常是最常见的线上故障来源之一。一定要给默认策略,或者至少记录一条清晰的 warn 日志,把入参和命中的 key 打出来。

4. 策略名不要用魔法字符串裸传。Python 可以用 Enum,Java 直接用枚举做 key,配合编译期检查,能避免大量手滑导致的 bug。

六、什么时候该上更重的方案

策略模式解决的是"分支多"的问题。但如果你的场景是:

  • 规则需要运营在后台动态配置、随时上下架;
  • 规则之间存在优先级、互斥、叠加等组合关系;
  • 规则数量上百,且变更频率以天计;

那么就该考虑规则引擎(如 Drools、LiteFlow、Aviator)或者把规则外置到配置中心/数据库里了。策略模式是重构的第一步,不是终点。

小结
改造前改造后
新增规则修改主函数,回归测试全量新增一个类/函数,单测覆盖自己
可读性深层嵌套,要看完整段才知道做什么类名即文档
可测性构造各种分支组合的入参每个策略独立测试
风险改一处影响全部影响面隔离

设计模式的价值从来不是让代码看起来高级,而是让改动变得安全。策略模式恰好是投入产出比最高的那一个:半小时就能学会,当天就能用上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询