策略模式(Strategy Pattern)实战拆解:从if-else泥潭到优雅策略的完整演进
先聊个场景:你维护一个订单系统,里面的折扣计算逻辑已经从最初的3个档位膨胀到了十几个。每次产品经理提需求,你都得在那个几百行的if-else里找一个合适的位置小心翼翼地插入新分支,改完还要提心吊胆地跑一遍全部回归。我敢打赌,任何在中大型项目里摸爬过几年的人,都见过甚至亲手写过这种代码。这种时候,策略模式(Strategy Pattern)就是那个能把烂摊子理顺的利器。
作为一个程序员,我大概从第四年写代码的时候才真正把策略模式用明白。这篇文章我会用实际项目的视角,把策略模式从概念到落地、从代码到经验,完整拆给你看。
1. 策略模式的核心思路:它到底解决什么问题
先搞清楚一件事:策略模式不是在炫技,它是为了解决实际问题的。我见过太多人为了用设计模式而用设计模式,结果把简单业务搞得无比复杂,这是完全走偏了才会干的事。
1.1 问题本质:算法的易变性与客户端耦合
任何一个业务系统,从诞生的第一天起就在面临变化。电商的促销规则、派送公司的运费计算、银行的手续费费率、内容平台的推荐权重,这些东西有一个共同特点:规则本身容易变,而且版本会越来越多。
如果你把所有这些规则都写成if-else或者switch-case塞在一个方法里,你会同时遇到三个麻烦:
第一个麻烦是难以维护。当方法体超过两百行,当嵌套条件超过四层,再勤快的程序员打开这套代码都会头大。第二个麻烦是违反开闭原则。每次新增规则,你必须修改已有的代码,也就是说你每次都在重新测试老功能。第三个麻烦是职责混乱。一个方法干了太多事情,既在做选择,又在做计算,耦合度高得吓人。
策略模式的核心思想说白了也很简单:定义一族算法,把它们一个个封装起来,并且使它们可以互相替换。它就是用“组合 + 委托”来替代继承和条件分支,把"做什么"和"怎么做"拆开,让调用方只关心策略的入口,不关心策略内部的操作细节。
1.2 策略模式的基本角色拆解
先看三个核心角色。
Context(上下文)是策略的使用者,它持有一个策略接口的引用,负责把请求委托给具体的策略对象。它不关心策略到底怎么实现,只知道自己可以调用策略的方法。
Strategy(策略接口)定义了一组算法的公共接口。不管具体策略怎么折腾,对Context来说,你只需要遵循这个契约。
ConcreteStrategy(具体策略)是策略接口的具体实现类,每个类封装了一种具体的算法或行为。需要新增规则时,加一个实现类就行,不需要动Context和其他策略。
光说概念有点绕,我用一个通俗类比帮大家建立直观印象:你出门上班,交通方式就是策略。地铁、公交、打车、骑车,每种方式都能把你送到公司,但它们的时间、成本、体验完全不同。出门前你要根据天气、时间、预算在它们之间做选择。这里"你"就是Context,"到达公司这个行为"是策略接口,"坐地铁/打车/骑车"就是一个个具体策略。你今天选地铁不会影响明天选打车,策略之间天然就是相互独立的。
2. 策略模式的代码落地:从订单折扣场景写起
概念理解了之后,最关心的肯定是代码怎么写。我用一个实际场景来演示:电商订单系统的折扣计算。
2.1 第一版:典型的if-else写法(痛点现场)
我们先把大家最眼熟的版本写出来,先感受一下痛点:
java复制// 第一版:传统if-else,这段代码已经是精简版了
public BigDecimal calculateDiscount(String userLevel, BigDecimal amount) {
if ("VIP".equals(userLevel)) {
return amount.multiply(new BigDecimal("0.8"));
} else if ("GOLD".equals(userLevel)) {
return amount.multiply(new BigDecimal("0.85"));
} else if ("SILVER".equals(userLevel)) {
return amount.multiply(new BigDecimal("0.9"));
} else if ("NEW_USER".equals(userLevel)) {
return amount.multiply(new BigDecimal("0.95"));
} else if ("BIRTHDAY_MONTH".equals(userLevel)) {
return amount.multiply(new BigDecimal("0.7"));
} else {
return amount;
}
}
这段代码初看好像还行,但如果业务再膨胀一下:权重叠加、满减优惠、店铺级折扣、会员等级动态调整、不同支付方式额外折扣,这个方法的行数会迅速飙升。更麻烦的是,这个类不光是电脑折扣,还有优惠券计算、运费减免,它们可能散落在不同的Service里,改起来每处都要动一遍。
2.2 第二版:策略模式重构,一个接口加N个实现
现在用策略模式重构。先定义一个策略接口:
java复制public interface DiscountStrategy {
/**
* 计算折扣后的金额
*
* @param orderAmount 订单原始金额
* @return 折扣后的金额
*/
BigDecimal discount(BigDecimal orderAmount);
/**
* 每个策略应该知道自己适用哪种用户(或场景)
*/
String strategyType();
}
这里我故意多加了一个strategyType()方法,后面会讲到这是一个非常实用的设计,能帮你省掉一整个工厂类的代码。
接着是具体策略实现。我写四个典型的,展示不同策略类之间的独立性:
java复制public class VipDiscountStrategy implements Disco
