【策略模式】最佳实践——Spring IoC实现策略模式全流程深度解析
简介
策略模式是一种行为型设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互相替换,并且使算法的变化不会影响使用算法的客户端。策略模式通过将具体的业务逻辑从上下文(Context)中剥离出来,独立为策略类,动态地将所需的行为注入上下文对象中,从而避免代码中充斥条件判断逻辑。
策略模式的核心由以下三部分组成:
- 策略接口(Strategy Interface):定义所有具体策略类的公共接口,用于约束具体策略的实现。
- 具体策略类(Concrete Strategy):实现策略接口,在内部封装具体的算法和业务逻辑。
- 上下文类(Context):持有一个具体策略对象的引用,用来调用具体策略类的方法。也可以使用工厂模式,将创建具体策略类的动作交给工厂类执行,实现业务逻辑、创建逻辑与应用程序的解耦合。
在下面的文章中,我将给策略模式的实现分为三步,从业务逻辑解耦、创建逻辑解耦到满足开闭原则,一步步实现策略模式的最佳实践。
1.策略模式(业务逻辑解耦)
设计一个支付系统,支持多种支付方式,包括支付宝(Alipay)、微信支付(WeChatPay)和银行卡支付(BankCardPay)。不同支付方式的逻辑独立,同时系统可以根据需求动态切换支付方式。
定义策略接口
// 定义支付策略接口
public interface PaymentStrategy {
void pay(double amount); // 支付方法
}
定义具体策略类
不同的具体策略类都统一继承策略接口,在类中重写策略接口定义的方法,编写具体的业务逻辑。
// 支付宝支付策略
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付了: ¥" + amount);
}
}
// 微信支付策略
public class WeChatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用微信支付了: ¥" + amount);
}
}
// 银行卡支付策略
public class BankCardStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用银行卡支付了: ¥" + amount);
}
}
定义上下文类
在上下文中定义策略类的引用,使用 set
方法注入具体策略类,通过方法调用执行具体策略类的业务逻辑。
// 支付上下文类
public class PaymentContext {
private PaymentStrategy strategy; // 策略引用
// 动态设置支付策略
public void setPaymentStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
// 执行支付
public void pay(double amount) {
if (strategy == null) {
throw new IllegalStateException("未设置支付策略!");
}
strategy.pay(amount); // 委托给具体策略
}
}
使用上下文类
在用户选用具体的支付方式时,需要给上下文 context
传入具体策略的对象,才可使用该策略的支付方法。(这种方式需要应用程序自己手动 new
出具体策略的对象,并且通过 set
方法传入给上下文类)
虽然通过这种方式,可以使得具体的支付业务逻辑与应用程序解耦合。但是依然存在两个问题:
- 具体策略类的创建逻辑仍然由应用程序执行,即策略类创建逻辑仍然与应用程序耦合。
- 如果需要添加新的策略,就需要再添加一个新的
if-else
分支,因此不符合开闭原则。(开闭原则通俗点说就是在添加新的功能时,不能修改现有的代码,这里添加一个新的分支就属于修改了现有的代码)
public class Main {
public static void main(String[] args) {
String type = "Alipay";
PaymentContext context = new PaymentContext();
if (type.equals("Alipay")) {
// 用户使用支付宝支付
context.setPaymentStrategy(new AlipayStrategy());
context.pay(15);
} else if (type.equals("Wechat")) {
context.setPaymentStrategy(new WeChatPayStrategy());
context.pay(10);
} else {
context.setPaymentStrategy(new BankCardStrategy());
context.pay(5);
}
}
}
执行结果如下:
开闭原则: 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
对扩展开放(Open for extension):软件实体应该允许在不改变其现有代码的情况下,通过增加新功能来对其进行扩展。也就是说,当软件的需求发生变化时,我们应该能够通过添加新代码来满足这些需求,而不需要修改已有的代码。
对修改关闭(Closed for modification):一旦软件实体被开发完成并投入使用,其源代码就不应该再被修改。这可以防止对现有功能的破坏,减少引入新的错误的风险,并使软件更加稳定和可维护。
2.引入工厂模式(创建逻辑解耦)
通过引入工厂模式,可以将创建具体策略类的动作转移给工厂类执行,将策略类的创建逻辑与应用程序解耦合。
定义工厂类
由工厂类负责策略类的创建逻辑,根据传入的不同参数,创建不同的具体策略类。这一步骤也就实现了策略类的创建逻辑与应用程序的解耦。
// 工厂类
public class PaymentStrategyFactory {
public static PaymentStrategy getStrategy(String type) {
switch (type) {
case "Alipay":
return new AlipayStrategy();
case "WeChatPay":
return new WeChatPayStrategy();
case "BankCard":
return new BankCardStrategy();
default:
throw new IllegalArgumentException("无效的支付类型");
}
}
}
改造工厂类
可以发现,如果需要使用大量的具体策略类的话,应用程序就需要编写大量的 if-else
、switch
分支。我们可以使用 Map
存储具体策略类,从而取消分支判断的设计。
public class PaymentStrategyFactory {
// 定义Map集合
private static final Map<String, PaymentStrategy> map = new HashMap<>();
//
static {
map.put("Alipay", new AlipayStrategy());
map.put("WeChatPay", new WeChatPayStrategy());
map.put("BankCard", new BankCardStrategy());
}
public static PaymentStrategy getStrategy(String type) {
// 如果存在该支付类型,返回对应的策略类
if (map.containsKey(type)) return map.get(type);
// 如果不存在该支付类型,抛出异常
else throw new IllegalArgumentException("无效的支付类型");
}
}
通过上述的改造,虽然解决了创建逻辑与应用程序耦合的问题,但是不符合开闭原则的问题仍然没有解决(如果需要添加新的具体策略类,还是需要修改工厂类的代码,在 Map
集合中 put
一个新的键值对)。
比如,如果要添加一个苹果支付的业务逻辑,就需要在 static
中添加这个代码,从而打破了开闭原则。
map.put("ApplePay", new ApplePayStrategy());
下文我们将使用 Spring IoC
解决这个问题。
使用工厂类
直接使用 PaymentStrategyFactory
工厂类的静态方法 getStrategy()
获取策略对象,调用策略对象的方法即可。
public class Main {
public static void main(String[] args) {
PaymentStrategy alipay = PaymentStrategyFactory.getStrategy("Alipay");
alipay.pay(10);
}
}
执行结果如下:
3.引入Spring IoC(满足开闭原则)
改造策略接口
需要在策略接口 PaymentStrategy
添加一个新的 mark
方法,用于标识每个接口。
public interface PaymentStrategy {
void pay(double amount); // 支付方法
String mark(); // 标识方法
}
改造具体策略类
具体策略类由于实现了策略接口,因此需要重写标识方法,这里直接返回对应策略的标识即可。(这一步骤很重要,应用程序通过工厂类获取策略类,就是通过这个标识获取的)
此外,我们还需要给每个具体策略类添加 @Component
注解,方便 Spring
容器管理具体策略类的生命周期。
通过 @Component
注解,实际上是通过 Spring
容器来执行 new
的步骤,也就是将具体策略类的创建逻辑由工厂类交给了 Spring 容器。
@Component:可以标记任意类为
Spring Bean
,Spring 容器会自动扫描和管理使用@Component
注解标注的类。
支付宝支付策略:
@Component
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付了: ¥" + amount);
}
// 返回策略的标识
@Override
public String mark() {
return "Alipay";
}
}
微信支付策略:
@Component
public class WeChatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用微信支付了: ¥" + amount);
}
@Override
public String mark() {
return "WeChatPay";
}
}
银行卡支付策略:
@Component
public class BankCardStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用银行卡支付了: ¥" + amount);
}
@Override
public String mark() {
return "BankCard";
}
}
改造工厂类
下面开始重头戏,我们将对工厂类进行改造,通过 Spring
容器帮我们管理策略类。
@Component
public class PaymentStrategyFactory implements InitializingBean {
// 注入IoC容器
@Autowired
private ApplicationContext context;
// 定义Map集合
private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();
// 获取对应标识的策略类
public PaymentStrategy getStrategy(String type) {
if (strategyMap.containsKey(type)) return strategyMap.get(type);
else throw new IllegalArgumentException("无效的支付类型");
}
@Override
public void afterPropertiesSet() throws Exception {
// 从IoC容器中获取所有策略接口 PaymentStrategy 的实现类,即具体策略类
Map<String, PaymentStrategy> beansOfType = context.getBeansOfType(PaymentStrategy.class);
beansOfType.forEach((key, value) -> strategyMap.put(value.mark(), value));
}
}
- 添加注解:首先需要给工厂类添加
@Component
注解,后续我们将通过依赖注入的方式使用该工厂类。
@Component
- 实现
InitializingBean
接口:通过重写该接口的afterPropertiesSet
方法,可以在Bean
的初始化阶段将Spring
容器创建好的具体策略类写入到工厂类的strategyMap
集合中。
Spring Bean 的生命周期:整体上可以简单分为四步:实例化 —> 属性赋值 —> 初始化 —> 销毁。
public class PaymentStrategyFactory implements InitializingBean
- 注入 IoC 容器:使用
@Autowired
注解注入ApplicationContext
,即 IoC 容器。方便后续从容器中获取 Bean 名称和策略接口实现类的Map
集合。
@Autowired
private ApplicationContext context;
- 定义 Map 集合:这个
Map
是工厂类存放策略类的集合,应用程序将从这个集合中获取对应的策略类。
// 定义Map集合
private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();
- 编写
afterPropertiesSet
方法逻辑:这里我们需要将 Bean 名称和策略类的键值对转化为标识和策略类的键值对,方便我们根据传入的标识直接获取策略类。
@Override
public void afterPropertiesSet() throws Exception {
// 4.1.从IoC容器中获取所有策略接口 PaymentStrategy 的实现类,即具体策略类
Map<String, PaymentStrategy> beansOfType = context.getBeansOfType(PaymentStrategy.class);
// 4.2.利用lambda表达式,将Spring容器中的策略类转移到工厂类的Map集合中
beansOfType.forEach((key, value) -> strategyMap.put(value.mark(), value));
}
第一步先从注入的 Spring
容器中获取容器创建好策略类的 Map
集合,该集合存储的是 Bean 名称和策略类的键值对,如下图:
第二步使用了 lambda
表达式(如果不熟练的话使用普通 for
循环也行),将Spring容器中的策略类转移到工厂类的Map集合中,其实这一步也就是将 key
值简化,方便根据标识字符串获取策略类。如下图:
- 编写
getStrategy
方法逻辑:最后一步,可以从strategyMap
集合中获取对应的策略类了。
// 获取对应标识的策略类
public PaymentStrategy getStrategy(String type) {
if (strategyMap.containsKey(type)) return strategyMap.get(type);
else throw new IllegalArgumentException("无效的支付类型");
}
工厂类到这里就彻底改造完毕了,我们通过将每个具体策略类都添加了 @Component
注解,在 Spring
启动时就会将这些使用了 @Component
注解的类创建出来并添加到 Spring IoC
容器中。这样,在添加新的策略类时就无需修改原有的工厂类,满足了开闭原则。
使用工厂类
@SpringBootTest
public class StrateTest {
// 注入工厂类
@Autowired
private PaymentStrategyFactory factory;
@Test
public void testStrategy() {
PaymentStrategy alipay = factory.getStrategy("Alipay");
alipay.pay(1000);
}
}
执行结果如下:
总结
最佳实践小结
通过上文的三个步骤,我们一步步将策略模式改造为了最佳实践,实现了业务逻辑、创建逻辑与应用程序的解耦以及满足了开闭原则。在Spring IoC
的策略模式实现中,主要做了这三件事情:
- 业务逻辑与应用程序解耦:通过定义策略接口,规范不同具体策略类的统一实现,将算法的业务逻辑交给了策略类实现。
- 创建逻辑与应用程序解耦:通过给策略类添加
@Component
注解,由Spring
扫描并注册具体策略类的对象到Spring
容器中,从而将策略类的创建逻辑转移到了Spring
容器。 - 满足开闭原则: 由于添加了
@Component
注解的策略类在Spring
启动后都被注册到了Spring
容器中,无需开发者手动硬编码到工厂类。在需要添加新的策略类时,Spring
容器可以帮助开发者自动注入。
对比其他模式
策略模式
- 定义:定义一系列算法,将每种算法封装在独立的策略类中,使得这些算法可以互相替换,且算法的变化不会影响使用算法的上下文对象。
- 核心思想:动态选择算法,运行时可以替换策略对象。
模板方法模式
- 定义:定义一个算法的框架,将算法的某些步骤延迟到子类中实现,使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。
- 核心思想:固定流程,子类负责填充或改写部分步骤。
工厂模式
- 定义:定义一个用于创建对象的接口,让子类决定实例化哪个具体类。工厂模式将对象的创建过程与具体类的逻辑解耦。
- 核心思想:对象的创建,由工厂负责生产特定对象。
者手动硬编码到工厂类。在需要添加新的策略类时,Spring
容器可以帮助开发者自动注入。
对比其他模式
策略模式
- 定义:定义一系列算法,将每种算法封装在独立的策略类中,使得这些算法可以互相替换,且算法的变化不会影响使用算法的上下文对象。
- 核心思想:动态选择算法,运行时可以替换策略对象。
模板方法模式
- 定义:定义一个算法的框架,将算法的某些步骤延迟到子类中实现,使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。
- 核心思想:固定流程,子类负责填充或改写部分步骤。
工厂模式
- 定义:定义一个用于创建对象的接口,让子类决定实例化哪个具体类。工厂模式将对象的创建过程与具体类的逻辑解耦。
- 核心思想:对象的创建,由工厂负责生产特定对象。