策略模式(Strategy Pattern)定义了一组同类型的算法,在不同的类中封装起来,每种算法可以根据当前场景相互替换,从而使算法的变化独立于使用它们的客户端(即算法的调用者)。
例如:在网购中,我在支付的时候,可以根据实际情况来选择不同的支付方式(微信支付、支付宝、银行卡支付等等),这些支付方式即是不同的策略。我们通常会看到如下的实现代码:
Order order = 订单信息
if (payType == 微信支付) {
微信支付流程
} else if (payType == 支付宝) {
支付宝支付流程
} else if (payType == 银行卡) {
银行卡支付流程
} else {
暂不支持的支付方式
}
如上代码,虽然写起来简单,但违反了面向对象的 2 个基本原则:
单一职责原则
:一个类只有1个发生变化的原因
之后修改任何逻辑,当前方法都会被修改开闭原则
:对扩展开放,对修改关闭
当我们需要增加、减少某种支付方式(积分支付/组合支付),或者增加优惠券等功能时,不可避免的要修改该段代码
特别是当 if-else
块中的代码量比较大时,后续的扩展和维护会变得非常复杂且容易出错。在阿里《Java开发手册》中,有这样的规则:超过3层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现
。
策略模式是解决过多 if-else
(或者 switch-case
) 代码块的方法之一,提高代码的可维护性、可扩展性和可读性。下面我将从策略的定义、创建和使用这三个方面以上述网购支付为示例来分别进行说明。
1. 策略的定义
策略接口的定义,通常包含两个方法:获取策略类型的方法和处理策略业务逻辑的方法。
**
* 第三方支付
*/
public interface Payment {
/**
* 获取支付方式
*
* @return 响应,支付方式
*/
PayTypeEnum getPayType();
/**
* 支付调用
*
* @param order 订单信息
* @return 响应,支付结果
*/
PayResult pay(Order order);
}
2.策略接口的实现
每种支付类都实现了上述接口(基于接口而非实现编程),这样我们可以灵活的替换不同的支付方式。下边示例代码展示了每种支付方式的实现:
/**
* 微信支付
*/
@Component
public class WxPayment implements Payment {
@Override
public PayTypeEnum getPayType() {
return PayTypeEnum.WX;
}
@Override
public PayResult pay(Order order) {
调用微信支付
if (成功) {
return PayResult.SUCCESS;
} else {
return PayResult.FAIL;
}
}
}
/**
* 支付宝支付
*/
@Component
public class AlipayPayment implements Payment {
@Override
public PayTypeEnum getPayType() {
return PayTypeEnum.ALIPAY;
}
@Override
public PayResult pay(Order order) {
调用支付宝支付
if (成功) {
return PayResult.SUCCESS;
} else {
return PayResult.FAIL;
}
}
}
/**
* 银行卡支付
*/
@Component
public class BankCardPayment implements Payment {
@Override
public PayTypeEnum getPayType() {
return PayTypeEnum.BANK_CARD;
}
@Override
public PayResult pay(Order order) {
调用银行卡支付
if (成功) {
return PayResult.SUCCESS;
} else {
return PayResult.FAIL;
}
}
}
3. 策略的创建
策略模式包含一组同类的策略,在使用时我们通常通过类型来判断创建哪种策略来进行使用。我们可以使用工厂模式来创建策略,以屏蔽策略的创建细节。如下代码所示:
public class PaymentFactory {
private static final Map<PayTypeEnum, Payment> payStrategies = new HashMap<>();
static {
payStrategies.put(PayTypeEnum.WX, new WxPayment());
payStrategies.put(PayTypeEnum.ALIPAY, new AlipayPayment());
payStrategies.put(PayTypeEnum.BANK_CARD, new BankCardPayment());
}
public static Payment getPayment(PayTypeEnum payType) {
if (payType == null) {
throw new IllegalArgumentException("pay type is empty.");
}
if (!payStrategies.containsKey(payType)) {
throw new IllegalArgumentException("pay type not supported.");
}
return payStrategies.get(payType);
}
}
或者使用 Spring
创建(自动获取spring容器中的Payment 接口实现类注册到 payStrategies):
@Component
public class PaymentFactory implements InitializingBean, ApplicationContextAware {
private static final Map<PayTypeEnum, Payment> payStrategies = new HashMap<>();
private ApplicationContext appContext;
public static Payment getPayment(PayTypeEnum payType) {
if (payType == null) {
throw new IllegalArgumentException("pay type is empty.");
}
if (!payStrategies.containsKey(payType)) {
throw new IllegalArgumentException("pay type not supported.");
}
return payStrategies.get(payType);
}
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
appContext = applicationContext;
}
@Override
public void afterPropertiesSet() {
// 将 Spring 容器中所有的 Payment 接口实现类注册到 payStrategies
appContext.getBeansOfType(Payment.class)
.values()
.forEach(payment -> payStrategies.put(payment.getPayType(), payment));
}
}
注意:以上两种创建方式,都是无状态的,即不包含成员变量,它们可以被共享使用(单例)。如果策略类是有状态的,需要根据业务场景每次创建新的策略对象,那么我们可以在工厂方法中,每次生成新的策略对象,而不是使用已经提前缓存好的策略对象。如下代码所示:
4. 策略的使用
通常我们事先并不知道会使用哪个策略,在程序运行时根据配置、用户输入、计算结果等来决定到底使用哪种策略。例如,前边支付方式的例子,我们会根据用户的选择来决定使用哪种支付方式。使用策略模式的代码实现如下:
Order order = 订单信息
PayResult payResult = PaymentFactory.getPayment(payType).pay(order);
if (payResult == PayResult.SUCCESS) {
System.out.println("支付成功");
} else if (payType == 支付宝) {
System.out.println("支付失败");
}
综上代码中,接口类只负责业务策略的定义,每个策略的具体实现单独放在实现类中,工厂类 Factory 只负责获取具体实现类,而具体调用代码则负责业务逻辑的编排。这些实现用到了面向接口而非实现编程,满足了职责单一、开闭原则,从而达到了功能上的高内聚低耦合、提高了可维护性、扩展性以及代码的可读性。
public class PaymentFactory {
public static Payment getPayment(PayTypeEnum payType) {
if (payType == null) {
throw new IllegalArgumentException("pay type is empty.");
}
if (payType == PayTypeEnum.WX) {
return new WxPayment();
}
if (payType == PayTypeEnum.ALIPAY) {
return new AlipayPayment();
}
if (payType == PayTypeEnum.BANK_CARD) {
return new BankCardPayment();
}
throw new IllegalArgumentException("pay type not supported.");
}
}