#策略模式
#一段代码引发的架构灾难
我们团队曾经有一段"经典"代码,在三年里被改了 47 次,最终成了一个 2000 行的怪物:
public class DiscountCalculator {
public double calculate(String userType, String promotionType, double amount) {
if ("VIP".equals(userType)) {
if ("FULLCUT".equals(promotionType)) {
return amount >= 200 ? amount - 50 : amount;
} else if ("DISCOUNT".equals(promotionType)) {
return amount * 0.8;
} else if ("COUPON".equals(promotionType)) {
return amount - 30;
}
} else if ("SVIP".equals(userType)) {
if ("FULLCUT".equals(promotionType)) {
return amount >= 100 ? amount - 80 : amount;
} else if ("DISCOUNT".equals(promotionType)) {
return amount * 0.7;
} else if ("COUPON".equals(promotionType)) {
return amount - 50;
}
} else if ("NORMAL".equals(userType)) {
if ("FULLCUT".equals(promotionType)) {
return amount >= 300 ? amount - 30 : amount;
} else if ("DISCOUNT".equals(promotionType)) {
return amount * 0.95;
} else if ("COUPON".equals(promotionType)) {
return amount - 10;
}
}
return amount;
}
}每次加一个用户类型或促销类型,都要在这堆 if-else 里再加分支。测试?每次改完都怕影响其他分支。
策略模式解决的就是这个问题:把每种算法/策略封装成独立类,让它们可以相互替换。
#二、策略模式核心结构
#2.1 标准写法
// 策略接口
interface DiscountStrategy {
double calculate(double amount);
}
// 具体策略:满减
class FullCutStrategy implements DiscountStrategy {
private final double threshold;
private final double cut;
public FullCutStrategy(double threshold, double cut) {
this.threshold = threshold;
this.cut = cut;
}
@Override
public double calculate(double amount) {
return amount >= threshold ? amount - cut : amount;
}
}
// 具体策略:打折
class DiscountStrategyImpl implements DiscountStrategy {
private final double rate;
public DiscountStrategyImpl(double rate) {
this.rate = rate;
}
@Override
public double calculate(double amount) {
return amount * rate;
}
}
// 具体策略:立减
class DirectCutStrategy implements DiscountStrategy {
private final double cut;
public DirectCutStrategy(double cut) {
this.cut = cut;
}
@Override
public double calculate(double amount) {
return amount - cut;
}
}#2.2 上下文类
class DiscountContext {
private DiscountStrategy strategy;
// 运行时注入策略
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double calculate(double amount) {
return strategy.calculate(amount);
}
}#2.3 使用方式
DiscountContext context = new DiscountContext();
// VIP 用户用满减
context.setStrategy(new FullCutStrategy(200, 50));
double vipPrice = context.calculate(300); // 250
// 普通用户用打折
context.setStrategy(new DiscountStrategyImpl(0.95));
double normalPrice = context.calculate(300); // 285现在,新增促销方式不需要改任何现有代码,只需要加一个新类:
// 新增策略:买二送一(折算折扣)
class BuyTwoGetOneStrategy implements DiscountStrategy {
private final double perUnitPrice;
private final int totalQuantity;
public BuyTwoGetOneStrategy(double perUnitPrice, int totalQuantity) {
this.perUnitPrice = perUnitPrice;
this.totalQuantity = totalQuantity;
}
@Override
public double calculate(double amount) {
int freeItems = totalQuantity / 3;
double effectiveQuantity = totalQuantity - freeItems;
return effectiveQuantity * perUnitPrice;
}
}#三、策略模式 + 工厂模式🔴
#3.1 消除 if-else 的工厂
class DiscountStrategyFactory {
private static final Map<String, DiscountStrategy> STRATEGIES = new HashMap<>();
static {
STRATEGIES.put("FULLCUT_VIP", new FullCutStrategy(200, 50));
STRATEGIES.put("DISCOUNT_SVIP", new DiscountStrategyImpl(0.7));
// 可以从配置文件/数据库加载
}
public static DiscountStrategy getStrategy(String type) {
DiscountStrategy strategy = STRATEGIES.get(type);
if (strategy == null) {
throw new IllegalArgumentException("Unknown strategy: " + type);
}
return strategy;
}
}配合 Spring,使用 @Component 自动注册:
@Configuration
public class StrategyConfig {
@Bean
public Map<String, DiscountStrategy> discountStrategies(
List<DiscountStrategy> strategyList) {
Map<String, DiscountStrategy> map = new HashMap<>();
for (DiscountStrategy s : strategyList) {
// 用策略类的简单类名作为 key
String name = s.getClass().getSimpleName()
.replace("Strategy", "").toLowerCase();
map.put(name, s);
}
return map;
}
}
@Service
class DiscountService {
private final Map<String, DiscountStrategy> strategies;
public DiscountService(Map<String, DiscountStrategy> strategies) {
this.strategies = strategies;
}
public double calculate(String type, double amount) {
return strategies.get(type).calculate(amount);
}
}#3.2 策略模式的 OCP 实现
新增促销类型时:
- if-else 方案:修改 DiscountCalculator 类(违反 OCP)
- 策略模式方案:新增 Strategy 类,修改工厂(遵守 OCP)【架构权衡】 策略模式的代价是类数量增加。每个策略一个类,10 个策略就是 10 个类。相比于 if-else 集中在一个文件里,策略模式让代码更分散。这是可扩展性和代码集中性的权衡。
#四、生产实战场景🟡
#4.1 支付渠道选择
interface PaymentStrategy {
PaymentResult pay(PaymentRequest request);
}
class AlipayStrategy implements PaymentStrategy { ... }
class WechatPayStrategy implements PaymentStrategy { ... }
class UnionPayStrategy implements PaymentStrategy { ... }
class CreditCardStrategy implements PaymentStrategy { ... }
@Service
class PaymentService {
private final Map<String, PaymentStrategy> strategies;
public PaymentService(Map<String, PaymentStrategy> strategies) {
this.strategies = strategies;
}
public PaymentResult pay(String channel, PaymentRequest request) {
PaymentStrategy strategy = strategies.get(channel);
if (strategy == null) {
throw new IllegalArgumentException("Unsupported channel: " + channel);
}
return strategy.pay(request);
}
}#4.2 排序策略
interface SortStrategy<T> {
List<T> sort(List<T> data);
}
class AscendingSortStrategy<T extends Comparable<T>> implements SortStrategy<T> {
@Override
public List<T> sort(List<T> data) {
return data.stream().sorted().collect(Collectors.toList());
}
}
class DescendingSortStrategy<T extends Comparable<T>> implements SortStrategy<T> {
@Override
public List<T> sort(List<T> data) {
return data.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
}
}
class PriceSortStrategy implements SortStrategy<Product> {
@Override
public List<Product> sort(List<Product> data) {
return data.stream()
.sorted(Comparator.comparing(Product::getPrice))
.collect(Collectors.toList());
}
}
class SalesVolumeSortStrategy implements SortStrategy<Product> {
@Override
public List<Product> sort(List<Product> data) {
return data.stream()
.sorted(Comparator.comparing(Product::getSalesVolume).reversed())
.collect(Collectors.toList());
}
}#4.3 验证策略
interface ValidationStrategy<T> {
boolean validate(T target);
String getErrorMessage();
}
class EmailValidationStrategy implements ValidationStrategy<String> {
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
@Override
public boolean validate(String email) {
return email != null && EMAIL_PATTERN.matcher(email).matches();
}
@Override
public String getErrorMessage() {
return "Invalid email format";
}
}
class PasswordValidationStrategy implements ValidationStrategy<String> {
@Override
public boolean validate(String password) {
return password != null
&& password.length() >= 8
&& password.matches(".*[A-Z].*")
&& password.matches(".*[0-9].*");
}
@Override
public String getErrorMessage() {
return "Password must be at least 8 characters with uppercase and digit";
}
}#五、策略模式 vs 状态模式
很多人分不清策略模式和状态模式。
| 维度 | 策略模式 | 状态模式 |
|---|---|---|
| 使用者意图 | 选择不同的算法/策略 | 对象状态变化时行为变化 |
| 切换方式 | 外部指定(客户端决定) | 内部驱动(状态自动切换) |
| 状态间关系 | 策略之间相互独立 | 状态之间有转换关系 |
| 上下文依赖 | 上下文不需要知道策略的切换 | 上下文需要管理状态转换 |
| 典型场景 | 支付方式、排序算法 | 订单状态、流程审批 |
// 策略模式:客户端决定用哪个策略
DiscountStrategy s = new VipDiscountStrategy();
context.setStrategy(s);
context.calculate(100);
// 状态模式:内部自动切换状态
class Order {
private OrderState state;
public void pay() {
state = state.pay(); // 状态自动切换到下一个
}
}#六、生产避坑清单
#6.1 策略选择逻辑的 if-else
// ❌ 错误:策略工厂里还有 if-else,等于没改
class DiscountStrategyFactory {
public static DiscountStrategy get(String type) {
if ("VIP".equals(type)) {
return new VipStrategy();
} else if ("SVIP".equals(type)) {
return new SvipStrategy();
}
// 还是 if-else
}
}
// ✅ 正确:用 Map 替代 if-else
class DiscountStrategyFactory {
private static final Map<String, DiscountStrategy> MAP = new HashMap<>();
static {
MAP.put("VIP", new VipStrategy());
MAP.put("SVIP", new SvipStrategy());
}
public static DiscountStrategy get(String type) {
return MAP.get(type);
}
}#6.2 策略类的数量爆炸
如果策略有 50 种,每种策略一个类会导致类爆炸。解决方案:
// 策略模板 + 参数化
class ParametricDiscountStrategy implements DiscountStrategy {
private final DiscountType type;
private final Map<String, Object> params;
public ParametricDiscountStrategy(DiscountType type, Map<String, Object> params) {
this.type = type;
this.params = params;
}
@Override
public double calculate(double amount) {
switch (type) {
case FULLCUT:
double threshold = (double) params.get("threshold");
double cut = (double) params.get("cut");
return amount >= threshold ? amount - cut : amount;
case DISCOUNT:
double rate = (double) params.get("rate");
return amount * rate;
default:
return amount;
}
}
}💡
策略模式和模板方法模式可以结合使用。当策略之间有共同逻辑时,用模板方法提取骨架,策略只实现差异部分。
#七、面试总结
#7.1 核心追问
- "策略模式和 if-else 相比有什么优势?" —— OCP、开闭原则
- "策略模式如何消除分支判断?" —— 工厂 + Map
- "策略模式和状态模式的区别是什么?" —— 外部指定 vs 内部转换
- "策略模式有什么缺点?" —— 类数量增加、策略选择本身可能有 if-else
#7.2 级别差异
| 级别 | 期望回答 |
|---|---|
| P5 | 能写出策略模式的基本结构 |
| P6 | 能说出和工厂模式的结合,知道如何消除策略选择的 if-else |
| P7 | 能对比策略模式和状态模式,能分析类数量爆炸的解决方案 |