策略模式:告别臃肿代码,实现算法的自由切换
在软件开发中,我们经常会遇到一个需求可以通过多种算法或策略来实现的情况。例如,一个支付功能可能支持支付宝、微信支付和银联支付;一个数据排序功能可能需要根据不同的场景选择快速排序、归并排序或冒泡排序;一个导航系统可能需要为不同的出行方式(驾车、公交、步行)计算不同的路线。
面对这样的需求,一个常见的、但不够优雅的做法是使用冗长的 if-else
或 switch-case
语句。这种做法虽然直接,却存在诸多弊端:代码臃肿、难以维护、违反开闭原则(对扩展开放,对修改关闭)。而策略模式正是为了解决这些问题而生的。
什么是策略模式?
策略模式 是一种行为型设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
简单来说,它就像是一个工具箱,里面有锤子、螺丝刀、扳手等各种工具(策略)。你需要拧螺丝时就拿出螺丝刀,需要敲钉子时就拿出锤子。策略模式让你可以自由地选择和更换“工具”,而无需改变你的“手”(客户端代码)。
模式的核心角色
策略模式通常包含三个核心角色:
- 策略:一个接口或抽象类,定义了所有支持算法的公共接口。
- 具体策略:实现了策略接口的具体算法类。
- 上下文:持有一个策略对象的引用,并用该策略对象来执行具体的算法。上下文通常提供一个接口,让客户端可以设置或切换策略。
策略模式的结构与代码示例
让我们通过一个经典的例子——电商系统中的支付功能——来深入理解策略模式。
步骤1:定义策略接口
首先,我们定义一个支付策略的接口,它声明了所有支付方式都必须实现的方法 pay
。
// 策略接口
public interface PaymentStrategy {
void pay(double amount);
}
步骤2:实现具体策略类
接着,我们实现几种不同的支付方式,它们都实现了 PaymentStrategy
接口。
// 具体策略类:支付宝支付
public class AlipayStrategy implements PaymentStrategy {
private String account;
public AlipayStrategy(String account) {
this.account = account;
}
@Override
public void pay(double amount) {
System.out.printf("使用支付宝账号 %s 支付了 %.2f 元。\n", account, amount);
// 这里调用支付宝的实际支付API...
}
}
// 具体策略类:微信支付
public class WechatPayStrategy implements PaymentStrategy {
private String openId;
public WechatPayStrategy(String openId) {
this.openId = openId;
}
@Override
public void pay(double amount) {
System.out.printf("使用微信OpenID %s 支付了 %.2f 元。\n", openId, amount);
// 这里调用微信支付的实际支付API...
}
}
// 具体策略类:信用卡支付
public class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
private String cvv;
public CreditCardStrategy(String cardNumber, String cvv) {
this.cardNumber = cardNumber;
this.cvv = cvv;
}
@Override
public void pay(double amount) {
System.out.printf("使用信用卡 %s 支付了 %.2f 元。\n", cardNumber, amount);
// 这里调用银行网关进行支付...
}
}
步骤3:创建上下文类
上下文类 PaymentContext
负责与策略交互。它持有一个支付策略,并提供一个执行支付的方法和一个可以动态设置策略的方法。
// 上下文类
public class PaymentContext {
private PaymentStrategy strategy;
// 通过构造器注入策略
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
// 通过Setter方法动态改变策略
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
// 执行支付
public void executePayment(double amount) {
strategy.pay(amount);
}
}
步骤4:客户端使用
最后,我们看看客户端如何灵活地使用这些策略。
public class Client {
public static void main(String[] args) {
double orderAmount = 100.50;
// 用户选择支付宝支付
PaymentContext context = new PaymentContext(new AlipayStrategy("zhangsan@alipay.com"));
context.executePayment(orderAmount);
System.out.println("-------------");
// 用户中途切换为微信支付(动态切换策略)
context.setStrategy(new WechatPayStrategy("wx_openid_123456"));
context.executePayment(orderAmount);
System.out.println("-------------");
// 新订单使用信用卡支付
PaymentContext newContext = new PaymentContext(new CreditCardStrategy("1234-5678-9012-3456", "123"));
newContext.executePayment(200.00);
}
}
输出结果:
使用支付宝账号 zhangsan@alipay.com 支付了 100.50 元。
-------------
使用微信OpenID wx_openid_123456 支付了 100.50 元。
-------------
使用信用卡 1234-5678-9012-3456 支付了 200.00 元。
通过这个例子,我们可以看到,当需要新增一种支付方式(如Apple Pay)时,我们只需要创建一个新的 ApplePayStrategy
类实现 PaymentStrategy
接口即可,完全不需要修改 PaymentContext
或客户端的其他代码。这完美地遵循了开闭原则。
策略模式的优缺点
优点
- 开闭原则:无需修改上下文即可引入新的策略。
- 消除条件判断:避免了冗长的条件判断语句(如
if-else
)。 - 代码复用:可以在系统的不同部分复用相同的策略。
- 算法隔离:将算法的实现细节与使用它的代码隔离开来。
缺点
- 客户端必须了解策略:客户端需要知道不同策略之间的区别,以便选择合适的策略。
- 增加对象数量:如果策略很多,会产生大量的具体策略类。
- 通信开销:策略和上下文之间可能需要通过接口传递数据,增加了通信开销。
实际应用场景
策略模式在现实世界的框架和库中无处不在:
- Java Collections Framework:
Collections.sort()
方法接受一个Comparator
参数,这就是一个策略接口,允许我们为排序定义不同的策略。 - Spring Framework:在Spring中,
ResourceLoader
用于加载资源(如类路径资源、文件系统资源、URL资源),不同的Resource
实现就是不同的策略。 - 支付网关集成:如上例所示,是策略模式的经典应用。
- 日志框架:如SLF4J,它作为一个门面,背后可以绑定Logback、Log4j2等不同的日志实现策略。
- 数据验证:一个字段的验证规则(非空、邮箱格式、手机号格式)可以定义为不同的验证策略。
总结
策略模式通过将算法封装成独立的类,提供了一种清晰、灵活的方式来管理和切换行为。它是应对算法频繁变化场景的利器,能够显著提高代码的可读性、可维护性和扩展性。
当你发现代码中充满了条件分支,且每个分支都执行着一种类似的但细节不同的操作时,不妨考虑使用策略模式来重构它。记住,优秀的软件设计不是一蹴而就的,而是在不断地识别变化、封装变化的过程中演进而来的。策略模式正是封装变化这一核心设计原则的完美体现。