Java 设计模式之策略模式:灵活切换算法的艺术
在软件开发中,我们经常需要为一个问题提供多种解决方案,并且希望能够根据不同场景灵活切换。比如排序算法有冒泡排序、快速排序、归并排序等;支付方式有微信支付、支付宝支付、银行卡支付等。如果将这些算法硬编码到业务逻辑中,会导致代码臃肿、难以维护。策略模式(Strategy Pattern)正是解决这类问题的最佳实践。
什么是策略模式?
策略模式是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户,从而实现算法的灵活切换。
这种模式的核心思想是分离算法的定义与使用,通过面向接口编程,使得客户端可以在运行时根据需要选择不同的算法。
策略模式的核心角色
策略模式通常包含三个核心角色:
- 抽象策略(Strategy):定义所有支持的算法的公共接口,通常是一个接口或抽象类
- 具体策略(Concrete Strategy):实现抽象策略定义的接口,提供具体的算法实现
- 环境类(Context):持有一个策略对象的引用,负责使用策略对象,客户端通过环境类来使用策略
策略模式的工作原理
- 定义抽象策略接口,声明所有具体策略都需要实现的方法
- 实现多个具体策略类,每个类对应一种具体的算法
- 环境类中维护一个抽象策略的引用,提供方法用于设置具体策略
- 客户端创建具体策略对象,通过环境类的方法设置策略
- 环境类在需要时调用策略对象的方法,执行具体算法
策略模式的代码实现
我们以 "购物车支付系统" 为例来实现策略模式。一个购物车需要支持多种支付方式,并且可以根据用户选择动态切换。
1. 抽象策略接口
首先定义支付策略的抽象接口,所有支付方式都需要实现这个接口:
// 支付策略接口
public interface PaymentStrategy {
// 支付方法
void pay(double amount);
// 获取支付方式名称
String getPaymentName();
}
2. 具体策略实现
实现不同的支付方式作为具体策略:
// 微信支付
public class WechatPayment implements PaymentStrategy {
private String wechatAccount;
public WechatPayment(String wechatAccount) {
this.wechatAccount = wechatAccount;
}
@Override
public void pay(double amount) {
System.out.println("使用微信支付 " + amount + " 元,账号:" + wechatAccount);
// 实际支付逻辑:调用微信支付API等
}
@Override
public String getPaymentName() {
return "微信支付";
}
}
// 支付宝支付
public class AlipayPayment implements PaymentStrategy {
private String alipayAccount;
public AlipayPayment(String alipayAccount) {
this.alipayAccount = alipayAccount;
}
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付 " + amount + " 元,账号:" + alipayAccount);
// 实际支付逻辑:调用支付宝支付API等
}
@Override
public String getPaymentName() {
return "支付宝支付";
}
}
// 银行卡支付
public class BankCardPayment implements PaymentStrategy {
private String cardNumber;
private String cardHolder;
public BankCardPayment(String cardNumber, String cardHolder) {
this.cardNumber = cardNumber;
this.cardHolder = cardHolder;
}
@Override
public void pay(double amount) {
System.out.println("使用银行卡支付 " + amount + " 元,卡号:" + maskCardNumber(cardNumber) + ",持卡人:" + cardHolder);
// 实际支付逻辑:调用银行支付API等
}
// 隐藏卡号中间部分,保护隐私
private String maskCardNumber(String cardNumber) {
if (cardNumber.length() <= 8) {
return cardNumber;
}
return cardNumber.substring(0, 4) + "****" + cardNumber.substring(cardNumber.length() - 4);
}
@Override
public String getPaymentName() {
return "银行卡支付";
}
}
3. 环境类实现
实现购物车作为环境类,使用支付策略进行支付:
import java.util.ArrayList;
import java.util.List;
// 购物车(环境类)
public class ShoppingCart {
private List<String> items;
private PaymentStrategy paymentStrategy;
public ShoppingCart() {
items = new ArrayList<>();
}
// 添加商品
public void addItem(String item) {
items.add(item);
}
// 移除商品
public void removeItem(String item) {
items.remove(item);
}
// 计算总价(简化处理,实际应根据商品价格计算)
public double calculateTotal() {
// 这里简化为每个商品10元
return items.size() * 10.0;
}
// 设置支付策略
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
// 执行支付
public void checkout() {
if (paymentStrategy == null) {
System.out.println("请选择支付方式");
return;
}
double total = calculateTotal();
System.out.println("购物车商品:" + items);
System.out.println("总价:" + total + "元");
paymentStrategy.pay(total);
System.out.println("支付完成!");
}
}
4. 客户端测试
public class ShoppingCartDemo {
public static void main(String[] args) {
// 创建购物车
ShoppingCart cart = new ShoppingCart();
// 添加商品
cart.addItem("Java编程思想");
cart.addItem("设计模式详解");
cart.addItem("算法导论");
// 选择微信支付
System.out.println("=== 使用微信支付 ===");
cart.setPaymentStrategy(new WechatPayment("wx123456789"));
cart.checkout();
// 选择支付宝支付
System.out.println("\n=== 使用支付宝支付 ===");
cart.setPaymentStrategy(new AlipayPayment("alipay987654321"));
cart.checkout();
// 选择银行卡支付
System.out.println("\n=== 使用银行卡支付 ===");
cart.setPaymentStrategy(new BankCardPayment("6222021234567890123", "张三"));
cart.checkout();
}
}
运行结果:
=== 使用微信支付 ===
购物车商品:[Java编程思想, 设计模式详解, 算法导论]
总价:30.0元
使用微信支付 30.0 元,账号:wx123456789
支付完成!
=== 使用支付宝支付 ===
购物车商品:[Java编程思想, 设计模式详解, 算法导论]
总价:30.0元
使用支付宝支付 30.0 元,账号:alipay987654321
支付完成!
=== 使用银行卡支付 ===
购物车商品:[Java编程思想, 设计模式详解, 算法导论]
总价:30.0元
使用银行卡支付 30.0 元,卡号:6222****0123,持卡人:张三
支付完成!
策略模式与工厂模式的结合
在实际开发中,策略模式常与工厂模式结合使用,由工厂负责创建具体策略对象,进一步降低客户端与具体策略的耦合。
我们添加一个支付策略工厂:
// 支付策略工厂
public class PaymentStrategyFactory {
// 根据支付方式名称创建对应的支付策略
public static PaymentStrategy createPaymentStrategy(String type, String... args) {
switch (type.toLowerCase()) {
case "wechat":
if (args.length < 1) {
throw new IllegalArgumentException("微信支付需要账号参数");
}
return new WechatPayment(args[0]);
case "alipay":
if (args.length < 1) {
throw new IllegalArgumentException("支付宝支付需要账号参数");
}
return new AlipayPayment(args[0]);
case "bankcard":
if (args.length < 2) {
throw new IllegalArgumentException("银行卡支付需要卡号和持卡人参数");
}
return new BankCardPayment(args[0], args[1]);
default:
throw new IllegalArgumentException("不支持的支付方式:" + type);
}
}
}
客户端使用工厂创建策略:
// 使用工厂模式创建支付策略
public class StrategyWithFactoryDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.addItem("数据结构");
cart.addItem("计算机网络");
// 通过工厂创建支付策略
PaymentStrategy wechatPay = PaymentStrategyFactory.createPaymentStrategy("wechat", "wx987654321");
cart.setPaymentStrategy(wechatPay);
cart.checkout();
}
}
策略模式的优缺点
优点
- 灵活性高:可以在运行时动态切换算法,无需修改原有代码
- 代码复用性好:将算法封装在独立的策略类中,便于复用
- 符合开闭原则:新增算法只需添加新的策略类,无需修改环境类和其他策略
- 避免大量条件判断:用多态替代复杂的 if-else 或 switch-case 语句
- 职责单一:每个策略类只负责实现一种算法,符合单一职责原则
缺点
- 类数量增加:每个算法都需要一个对应的策略类,可能导致类数量增多
- 客户端需要了解策略:客户端必须知道所有策略的存在,才能选择合适的策略
- 策略间通信问题:策略类之间无法直接通信,如需共享数据需通过环境类
策略模式的适用场景
- 当一个问题有多种解决方案,且需要在不同场景下切换时
- 当代码中存在大量与算法相关的条件判断语句时
- 当需要隐藏算法的实现细节,只暴露其接口时
- 当希望客户端可以扩展或替换现有算法时
常见应用场景:
- 排序算法的选择
- 不同的校验规则
- 多种支付方式
- 不同的日志记录方式
- 各种压缩算法
总结
策略模式通过分离算法的定义与使用,实现了算法的灵活切换和扩展,有效解决了条件判断语句臃肿的问题。它将每个算法封装成独立的策略类,使得算法可以独立于客户端变化。
在实际开发中,策略模式特别适合处理那些具有多种变体的业务逻辑。合理运用策略模式,可以使代码更加清晰、灵活,也更易于维护和扩展。
需要注意的是,策略模式增加了类的数量,并且要求客户端了解不同策略的区别,因此在策略数量不多且变化不频繁的场景下,过度使用策略模式可能会适得其反。