策略模式:如何用一行 @Autowired Map 消灭所有 if-else?
一句话结论(30s)
策略模式的本质是把「可替换的算法」从调用方抽离成独立策略,让新增策略零修改主流程。关键设计是 Spring 的 @Autowired Map<String, PayStrategy> 一行注入——容器自动收集所有策略实现并以 Bean 名为 key 组装成 Map。权衡:策略类数量会增多,但因为消除了 if-else 分支、完全符合开闭原则,值得。
核心原理(2min)
主流程:定义策略接口 → 每个算法实现一个 @Component 策略类并返回自己的 getType() 标识 → 容器注入 Map<String, PayStrategy> → 调用方 strategyMap.get(type) 路由后执行 pay()。关键机制在 Spring 依赖注入:当注入目标类型为 Map<String, T> 时,容器收集所有 T 类型 Bean、以 Bean 名称为 key 注入 Map,于是策略的「注册、发现、路由」全部交给容器,调用端只剩一行 get。源码落地:Spring 的 HandlerMethodArgumentResolver、Resource 按类型路由,以及支付/通知/优惠券等多渠道业务路由都采用此模式;它与状态模式结构相同,区别只在策略由调用方显式选择、状态由内部状态机自动切换。
底层深入(5-10min)
问题:支付方式选择的 if-else
public void pay(String type, BigDecimal amount) {
if ("wechat".equals(type)) {
// 微信支付逻辑
} else if ("alipay".equals(type)) {
// 支付宝逻辑
} else if ("bankCard".equals(type)) {
// 银行卡逻辑
} else {
throw new IllegalArgumentException("不支持");
}
}
每次新增支付方式都要改这个方法 → 违背开闭原则 → 有一天 if-else 变成 20 个分支 → 圈复杂度爆炸。
💭 思考穿插:if-else 到底哪里有问题?分支少时它完全没问题,但它的成本随分支线性增长——每加一种支付方式都要改这段代码、都要重新测试这条路径,20 个分支的圈复杂度也让人难读难维护。策略模式的核心不是「去掉 if-else 这个语法」,而是「把可替换算法从调用方抽离」,让新增不再动主流程。
策略模式的标准实现
// 策略接口
public interface PayStrategy {
void pay(BigDecimal amount);
String getType(); // 返回策略标识
}
// 各策略实现
@Component
public class WechatPayStrategy implements PayStrategy {
public String getType() { return "wechat"; }
public void pay(BigDecimal amount) { /* 微信支付 */ }
}
@Component
public class AlipayStrategy implements PayStrategy {
public String getType() { return "alipay"; }
public void pay(BigDecimal amount) { /* 支付宝支付 */ }
}
Spring 的一行注入魔法
@Service
public class PayContext {
@Autowired
private Map<String, PayStrategy> strategyMap;
public void pay(String type, BigDecimal amount) {
PayStrategy strategy = strategyMap.get(type);
if (strategy == null) throw new IllegalArgumentException("不支持");
strategy.pay(amount);
}
}
Spring 检测到注入目标类型是 Map<String, T> → 收集所有实现 T 的 Bean → 以 Bean 名称为 key 注入到 Map 中。
新增一种支付方式只需新建一个 @Component 类实现 PayStrategy,零修改 PayContext。策略注册、发现、路由全由 Spring 容器完成。
💭 思考穿插:
Map<String, PayStrategy>的 key 到底是「Bean 名」还是getType()的返回值?——是 Bean 名。Spring 按每个策略类的 Bean 名做 key 注入 Map,而路由时strategyMap.get(type)用的是getType()的返回值,所以两者必须对齐。很多人踩的坑是:Bean 名默认是「类名首字母小写」(wechatPayStrategy),而getType()返回get就取不到。稳妥做法是用@Component("wechat")显式把 Bean 名设成和getType()一致。
Spring 源码:BeanFactory 的实例化策略
Spring 的 InstantiationStrategy 就是一个教科书式的策略模式——把「Bean 如何被实例化」抽成策略接口,让不同实例化算法(纯反射、CGLIB 方法注入)可插拔。
策略接口(三个 instantiate 重载分别对应「无参构造 / 指定构造器 / 工厂方法」三种实例化入口):
// org.springframework.beans.factory.support.InstantiationStrategy
public interface InstantiationStrategy {
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)
throws BeansException;
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
Constructor<?> ctor, Object... args) throws BeansException;
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Object factoryBean, Method factoryMethod, @Nullable Object... args)
throws BeansException;
default Class<?> getActualBeanClass(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
return bd.getBeanClass();
}
}
简单实现:直接用反射调构造器,不支持方法注入:
// org.springframework.beans.factory.support.SimpleInstantiationStrategy
public class SimpleInstantiationStrategy implements InstantiationStrategy {
@Override
public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides.
if (!bd.hasMethodOverrides()) {
Constructor<?> constructorToUse;
synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) {
Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
constructorToUse = clazz.getDeclaredConstructor();
bd.resolvedConstructorOrFactoryMethod = constructorToUse;
}
catch (Throwable ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
}
}
}
return BeanUtils.instantiateClass(constructorToUse);
}
else {
// Must generate CGLIB subclass.
return instantiateWithMethodInjection(bd, beanName, owner);
}
}
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
}
}
默认实现(继承 + 增强):当 Bean 声明了 lookup-method / replaced-method 这类方法注入时,用 CGLIB 动态生成子类来重写目标方法:
// org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
return instantiateWithMethodInjection(bd, beanName, owner, null);
}
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Constructor<?> ctor, Object... args) {
return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
}
}
按类型持有策略(策略的注入点):AbstractAutowireCapableBeanFactory 用接口类型持有策略引用,构造时默认 new CglibSubclassingInstantiationStrategy(),并提供 setter 允许运行时替换:
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
/** Strategy for creating bean instances. */
private InstantiationStrategy instantiationStrategy;
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
this.instantiationStrategy = new CglibSubclassingInstantiationStrategy();
}
public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) {
this.instantiationStrategy = instantiationStrategy;
}
public InstantiationStrategy getInstantiationStrategy() {
return this.instantiationStrategy;
}
}
这里和前面的 Map<String, PayStrategy> 是同一个模式的两个变体:Map 注入是「收集多个实现按名称路由」,BeanFactory 是「持有一个接口引用按需替换」。调用方 AbstractAutowireCapableBeanFactory 只面向 InstantiationStrategy 接口编程,从不关心底层是反射还是 CGLIB,新增/替换实例化算法只需换一个实现类,完全符合开闭原则。CglibSubclassingInstantiationStrategy 通过继承 SimpleInstantiationStrategy 复用无方法注入时的反射逻辑,只重写 instantiateWithMethodInjection 钩子——这正是策略模式叠加模板方法模式的组合拳。
💭 思考穿插:Map 注入和 BeanFactory 的
InstantiationStrategy为什么是同一个模式?——Map 注入是「收集多个实现、按名称路由」,面向”多选一”;InstantiationStrategy是「持有一个接口引用、按需替换」,面向”运行时换一个”。两者都是面向接口编程、新增实现不碰调用方,区别只在「策略集合的形态」:一个是一张 Map,一个是单个引用。
策略 vs 工厂 vs 状态
| 策略 | 工厂 | 状态 | |
|---|---|---|---|
| 目的 | 算法可替换 | 创建对象并隐藏实现 | 状态改变行为 |
| 选择时机 | 运行时由调用方决定 | 创建时由工厂决定 | 状态变化自动切换 |
| 典型特征 | 调用方传 key 选择 | 方法直接返回产品 | 内部状态机驱动 |
策略和状态在结构上几乎一样(都组合了一个接口引用),区别在使用方式:策略由外部选择哪个算法(调用方知悉),状态由内部状态机自动切换(调用方不知情)。
💭 思考穿插:策略和状态结构几乎一样,怎么一眼区分?——看「谁决定换」。策略由调用方在运行时显式选(调用方知道有哪些算法,自己传 key);状态由对象内部的状态机自动切换(调用方根本不知道有状态在变)。所以判断标准是:换算法这个动作,是「外部指令」还是「内部自转」。
总结
策略模式在 Spring 中的最佳实践:@Autowired Map<String, Strategy> 一行注入,全部 if-else 归零。 新增策略只需加 @Component 类,Spring 容器自动注册到 Map,完全符合开闭原则。
章末提问
Q1:策略模式解决什么问题?为什么不用 if-else?
结论先行:解决「可替换算法」的扩展问题,新增策略零修改主流程,符合开闭原则。 因为:if-else 每加一个分支都要改主流程、重新测试,分支多了圈复杂度爆炸;策略把算法抽成独立类,新增只加一个
@Component类,主流程一行不改。
Q2:Spring 的 Map 注入怎么把策略收集起来?key 是什么?
结论先行:当注入目标类型是
Map<String, T>时,容器收集所有 T 的 Bean、以 Bean 名为 key 注入;路由时用getType()的返回值去get。 因为:Spring 对 Map 类型注入有特殊处理,key 默认是 Bean 名(类名首字母小写)。所以要保证「Bean 名」和getType()对齐,否则get取不到——常见做法是@Component("wechat")显式指定 Bean 名。
Q3:策略模式和工厂模式有什么区别?
结论先行:策略关注「算法可替换」,工厂关注「对象创建并隐藏实现」;选择时机也不同。 因为:策略在运行时由调用方决定用哪个算法(传 key 路由);工厂在创建时由工厂决定返回哪个产品(方法返回产品)。工厂关注「造什么」,策略关注「用哪个算法做」。
Q4:策略模式和状态模式有什么区别?
结论先行:结构几乎一样,区别在「谁决定切换」——策略由调用方显式选,状态由内部状态机自动切换。 因为:策略的调用方知道自己有哪些算法、主动传 key;状态的调用方不知道状态在变,是对象内部根据状态自动换实现。判断标准是「换算法是外部指令还是内部自转」。
Q5:策略模式有什么代价?什么场景不该用?
结论先行:代价是策略类数量增多;只有两三个分支且基本不扩展时不该用。 因为:每个算法一个类会增加类的数量和维护成本;如果分支少且稳定,if-else 反而简单直接,过度设计不值得。判断标准是「扩展频率」——分支会持续增长才值得上策略。