一、什么是策略模式?
- 定义: 策略模式是一种行为型设计模式。 它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。
- 核心思想: 将算法的定义与使用分离。 客户端代码不直接调用具体的算法,而是通过一个统一的接口(策略接口)来访问不同的算法。
- 意图: 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
二、策略模式的结构
策略模式通常包含以下几个角色:
-
Context (环境/上下文):
- 维护一个对 Strategy 对象的引用。
- 定义客户端使用的接口。
- 可以包含一些与算法无关的业务逻辑。
- 不直接与具体策略类交互,而是通过 Strategy 接口与策略类交互。
-
Strategy (策略接口):
- 定义所有支持的算法的公共接口。
- 通常是一个接口或抽象类。
-
ConcreteStrategy (具体策略):
- 实现 Strategy 接口,提供具体的算法实现。
- 可以有多个 ConcreteStrategy 类,每个类实现一种不同的算法。
UML 类图:
+-----------------+ +-----------------+ +---------------------+
| Context |-------->| <<Strategy>> | | ConcreteStrategyA |
+-----------------+ +-----------------+ +---------------------+
| -strategy | | +algorithm() |------>| +algorithm() |
+-----------------+ +-----------------+ +---------------------+
| +contextInterface()|
+-----------------+
+---------------------+
| ConcreteStrategyB |
+---------------------+
| +algorithm() |
+---------------------+
+---------------------+
| ConcreteStrategyC |
+---------------------+
| +algorithm() |
+---------------------+
三、策略模式的实现 (Java)
java">// 策略接口
interface PaymentStrategy {
void pay(int amount);
}
// 具体策略类 - 支付宝支付
class AlipayStrategy implements PaymentStrategy {
private String email;
private String password;
public AlipayStrategy(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Alipay.");
// ... 支付宝支付的具体逻辑 ...
}
}
// 具体策略类 - 微信支付
class WeChatPayStrategy implements PaymentStrategy {
private String appId;
private String secretKey;
public WeChatPayStrategy(String appId, String secretKey) {
this.appId = appId;
this.secretKey = secretKey;
}
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using WeChat Pay.");
// ... 微信支付的具体逻辑 ...
}
}
// 具体策略类 - 银行卡支付
class CreditCardStrategy implements PaymentStrategy {
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public CreditCardStrategy(String cardNumber, String cvv, String dateOfExpiry) {
this.cardNumber = cardNumber;
this.cvv = cvv;
this.dateOfExpiry = dateOfExpiry;
}
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card.");
// ... 银行卡支付的具体逻辑 ...
}
}
// 上下文类
class ShoppingCart {
private List<Item> items;
private PaymentStrategy paymentStrategy; // 持有策略接口的引用
public ShoppingCart() {
this.items = new ArrayList<>();
}
public void addItem(Item item) {
this.items.add(item);
}
public void removeItem(Item item) {
this.items.remove(item);
}
public int calculateTotal() {
int sum = 0;
for (Item item : items) {
sum += item.getPrice();
}
return sum;
}
// 设置支付策略
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
// 使用支付策略进行支付
public void pay() {
int amount = calculateTotal();
paymentStrategy.pay(amount); // 通过策略接口调用具体算法
}
}
// 商品类 (示例)
class Item {
private String upcCode;
private int price;
public Item(String upcCode, int price) {
this.upcCode = upcCode;
this.price = price;
}
public String getUpcCode() {
return upcCode;
}
public int getPrice() {
return price;
}
}
// 客户端代码
public class StrategyPatternExample {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
Item item1 = new Item("1234", 10);
Item item2 = new Item("5678", 40);
cart.addItem(item1);
cart.addItem(item2);
// 使用支付宝支付
cart.setPaymentStrategy(new AlipayStrategy("myemail@example.com", "mypwd"));
cart.pay();
// 使用微信支付
cart.setPaymentStrategy(new WeChatPayStrategy("myAppId", "mySecretKey"));
cart.pay();
// 使用银行卡支付
cart.setPaymentStrategy(new CreditCardStrategy("1234567890123456", "786", "12/24"));
cart.pay();
}
}
代码解释:
PaymentStrategy
(策略接口): 定义了支付的公共接口pay()
。AlipayStrategy
、WeChatPayStrategy
、CreditCardStrategy
(具体策略): 实现了PaymentStrategy
接口,提供了不同的支付方式。ShoppingCart
(上下文): 维护了一个PaymentStrategy
类型的引用,可以在运行时设置不同的支付策略。pay()
方法通过paymentStrategy
对象调用具体的支付算法。
四、策略模式的优缺点
优点:
- 开闭原则: 增加新的策略非常方便,无需修改原有代码,只需要添加新的具体策略类即可。
- 避免使用多重条件判断: 将不同的算法封装到不同的策略类中,避免了使用
if-else
或switch-case
等多重条件判断语句。 - 提高算法的复用性: 不同的策略类可以被多个上下文对象复用。
- 提高算法的保密性: 客户端不需要知道算法的具体实现细节。
- 松耦合: 算法的定义与使用分离,降低了代码的耦合度。
缺点:
- 客户端必须知道所有的策略类: 客户端需要知道所有的策略类,并选择合适的策略类。
- 增加类的数量: 每增加一个策略,就需要增加一个具体策略类,可能会导致类的数量过多。
- 只适用于算法或行为的选择: 策略模式只适用于封装算法或行为,不适用于封装对象的状态。
- 策略间通信: 如果策略之间需要共享数据或进行通信,可能需要额外的机制来处理。
五、策略模式的适用场景
- 需要在运行时动态地选择算法: 例如,根据用户的选择、系统配置或外部条件来选择不同的算法。
- 有多种算法或行为可供选择: 例如,排序算法(冒泡排序、快速排序、归并排序)、压缩算法(ZIP、GZIP、RAR)、加密算法(AES、DES、RSA)等。
- 需要避免使用多重条件判断语句: 如果代码中存在大量的
if-else
或switch-case
语句来根据不同的条件选择不同的算法,可以考虑使用策略模式。 - 需要对客户端隐藏算法的具体实现细节: 可以将算法的实现细节封装在策略类中,只向客户端暴露策略接口。
六、策略模式的应用示例
- 排序算法: 定义一个排序策略接口,不同的排序算法(例如冒泡排序、快速排序、归并排序)实现该接口。 客户端可以根据需要选择不同的排序算法。
- 压缩算法: 定义一个压缩策略接口,不同的压缩算法(例如 ZIP、GZIP、RAR)实现该接口。 客户端可以根据需要选择不同的压缩算法。
- 加密算法: 定义一个加密策略接口,不同的加密算法(例如 AES、DES、RSA)实现该接口。 客户端可以根据需要选择不同的加密算法。
- 支付方式: 定义一个支付策略接口,不同的支付方式(例如支付宝、微信支付、银行卡支付)实现该接口。 客户端可以根据需要选择不同的支付方式。
- Java I/O 中的
Comparator
接口:Comparator
接口定义了比较两个对象的方法。 可以创建不同的Comparator
实现类来定义不同的比较策略。
七、策略模式 vs. 模板方法模式 vs. 状态模式
- 策略模式 (Strategy): 关注的是 算法的替换,通过组合和接口来实现算法的切换。
- 模板方法模式 (Template Method): 关注的是 算法的骨架,通过继承和重写来实现算法的可变部分。
- 状态模式 (State): 关注的是 对象的状态变化,通过状态对象来改变对象的行为。
区别:
特征 | 策略模式 | 模板方法模式 | 状态模式 |
---|---|---|---|
关注点 | 算法选择 | 算法骨架 | 对象状态 |
实现方式 | 组合/委托 | 继承 | 组合/委托 |
可变部分 | 不同的策略类实现相同的接口 | 子类实现抽象方法或覆盖钩子方法 | 不同的状态类实现相同的接口 |
算法结构 | 策略类定义算法,客户端选择具体的策略 | 模板方法定义算法结构,子类实现具体步骤 | 状态类定义状态下的行为,Context 对象根据状态切换行为 |
控制反转 | 有 | 有 | 有 |
适用场景 | 定义一系列算法,并在运行时动态选择算法 | 定义算法骨架,部分步骤延迟到子类实现 | 对象行为取决于状态,且状态变化复杂 |
总结:
策略模式是一种非常有用的设计模式,它可以将算法的定义与使用分离,提高代码的可扩展性、可维护性和可复用性。