设计模式--策略模式 (Strategy)
策略模式将一系列算法分别封装,使其可相互替换。它让算法的变化独立于使用者,有效避免了代码中大量的 if-else 判断。
我们来详细、清晰地讲解一下设计模式中的策略模式(Strategy Pattern)。
1. 什么是策略模式?
一句话定义:
策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。此模式让算法的变化独立于使用算法的客户。
通俗理解:
想象一下你去某个地方旅游,你可以选择多种交通方式:
- 坐飞机(速度快,价格高)
- 坐火车(速度适中,价格适中)
- 自己开车(灵活,但可能累)
对于“去旅游”这个行为(我们称之为上下文 Context),具体采用哪种交通方式(我们称之为策略 Strategy),是可以随时切换的。你今天可以决定坐飞机去,明天可以决定开车回来。
策略模式做的就是这件事:它将这些交通方式(算法)各自封装成独立的类,然后让你的“旅行计划”(上下文)持有一个交通方式的引用。当你需要改变出行方式时,只需更换这个引用的具体对象即可,而无需修改“旅行计划”本身的代码。
2. 解决什么问题?
策略模式主要解决在一个类中存在大量 if-else 或 switch-case 语句的问题。当一个操作有多种不同的实现方式,并且你希望在运行时根据不同情况选择其中一种时,通常会写出这样的代码:
反例(不使用策略模式):
class PaymentService {
public void processPayment(String type, double amount) {
if ("Alipay".equals(type)) {
System.out.println("使用支付宝支付 " + amount + " 元");
// 支付宝支付的复杂逻辑...
} else if ("WeChatPay".equals(type)) {
System.out.println("使用微信支付 " + amount + " 元");
// 微信支付的复杂逻辑...
} else if ("CreditCard".equals(type)) {
System.out.println("使用信用卡支付 " + amount + " 元");
// 信用卡支付的复杂逻辑...
} else {
System.out.println("不支持的支付方式");
}
}
}
这种写法的缺点:
- 违反开放/封闭原则:如果需要增加一种新的支付方式(比如银联支付),就必须修改
processPayment方法的内部代码,这增加了引入错误的风险。 - 代码臃肿,难以维护:随着支付方式的增多,
if-else结构会变得越来越长,代码可读性和可维护性急剧下降。 - 复用性差:支付逻辑被硬编码在
PaymentService类中,无法在其他地方复用。
策略模式通过将这些“分支逻辑”提取出来,封装到各自独立的策略类中,从而解决了上述问题。
3. 策略模式的结构
策略模式包含三个核心角色:
Context (上下文)
- 它持有一个
Strategy接口的引用。 - 它不关心具体算法的实现细节,只负责在需要时调用策略对象的方法。
- 它通常提供一个方法来让客户端设置(或切换)具体的策略对象。
- 它持有一个
Strategy (抽象策略)
- 通常是一个接口或抽象类。
- 它定义了所有支持的算法的公共接口。上下文类通过这个接口调用具体的策略。
ConcreteStrategy (具体策略)
- 实现了
Strategy接口。 - 封装了具体的算法或行为。
- 每个具体策略类都代表一种算法实现。
- 实现了
UML 结构图:
+----------------+ +------------------+
| Context |<>--->| IStrategy |
+----------------+ +------------------+
| - strategy | | + doAlgorithm() |
| + setStrategy()| +------------------+
| + execute() | ^
+----------------+ |
| |
+------------------------+
| |
+------------------+ +------------------+
| ConcreteStrategyA| | ConcreteStrategyB|
+------------------+ +------------------+
| + doAlgorithm() | | + doAlgorithm() |
+------------------+ +------------------+
Context通过组合关系持有一个IStrategy对象。ConcreteStrategyA和ConcreteStrategyB都实现了IStrategy接口。
4. 代码示例
我们用一个商场打折的场景来重构上面的支付例子,这个场景更经典。假设一个订单有多种优惠策略:无优惠、9折优惠、满300减50。
第1步:定义抽象策略 (Strategy)
创建一个接口,定义计算价格的通用方法。
// 1. 抽象策略接口: DiscountStrategy
interface DiscountStrategy {
double calculateDiscount(double price);
}
第2步:创建具体策略 (ConcreteStrategy)
为每一种优惠方式创建一个具体的实现类。
// 2.1 具体策略A: 无优惠
class NoDiscountStrategy implements DiscountStrategy {
@Override
public double calculateDiscount(double price) {
System.out.println("无优惠");
return price;
}
}
// 2.2 具体策略B: 9折优惠
class PercentageDiscountStrategy implements DiscountStrategy {
private final double percentage;
public PercentageDiscountStrategy(double percentage) {
this.percentage = percentage;
}
@Override
public double calculateDiscount(double price) {
System.out.println("享受" + (percentage * 10) + "折优惠");
return price * percentage;
}
}
// 2.3 具体策略C: 满减优惠
class FullReductionDiscountStrategy implements DiscountStrategy {
private final double threshold; // 满减门槛
private final double reduction; // 减免金额
public FullReductionDiscountStrategy(double threshold, double reduction) {
this.threshold = threshold;
this.reduction = reduction;
}
@Override
public double calculateDiscount(double price) {
if (price >= threshold) {
System.out.println("满" + threshold + "减" + reduction);
return price - reduction;
}
return price;
}
}
第3步:创建上下文 (Context)
Order (订单) 类就是我们的上下文,它需要根据不同的策略来计算最终价格。
// 3. 上下文类: Order
class Order {
private double originalPrice;
private DiscountStrategy discountStrategy; // 持有策略接口的引用
public Order(double originalPrice, DiscountStrategy discountStrategy) {
this.originalPrice = originalPrice;
this.discountStrategy = discountStrategy;
}
// 提供一个方法来切换策略
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
// 执行策略
public double getFinalPrice() {
if (discountStrategy != null) {
return discountStrategy.calculateDiscount(originalPrice);
}
return originalPrice;
}
}
第4步:客户端调用
客户端根据需要创建和设置不同的策略。
public class Client {
public static void main(String[] args) {
// 场景1: 客户是普通会员,无优惠
DiscountStrategy noDiscount = new NoDiscountStrategy();
Order order1 = new Order(500, noDiscount);
System.out.println("订单1最终价格: " + order1.getFinalPrice());
System.out.println("--------------------");
// 场景2: 客户是VIP会员,享受9折优惠
DiscountStrategy percentageDiscount = new PercentageDiscountStrategy(0.9);
Order order2 = new Order(500, percentageDiscount);
System.out.println("订单2最终价格: " + order2.getFinalPrice());
System.out.println("--------------------");
// 场景3: 参与满300减50活动
DiscountStrategy fullReductionDiscount = new FullReductionDiscountStrategy(300, 50);
Order order3 = new Order(500, fullReductionDiscount);
System.out.println("订单3最终价格: " + order3.getFinalPrice());
System.out.println("--------------------");
// 场景4: 同一个订单,在运行时切换优惠策略
System.out.println("订单3在运行时切换策略...");
order3.setDiscountStrategy(new PercentageDiscountStrategy(0.8)); // 切换为8折
System.out.println("订单3切换策略后最终价格: " + order3.getFinalPrice());
}
}
输出结果:
```
无优惠
订单1最终价格: 500.0
享受9.0折优惠
订单2最终价格: 450.0
满300.0减50.0
订单3最终价格: 450.0
订单3在运行时切换策略...
享受8.0折优惠
订单3切换策略后最终价格: 400.0
---
### 5. 优缺点
优点:
1. 符合开放/封闭原则:新增一种策略时,只需增加一个新的具体策略类,无需修改上下文或其他已有代码。
2. 避免多重条件判断:将 `if-else` 的巨大分支结构转变为一系列独立的策略类,使代码更清晰、易于维护。
3. 算法可以自由切换:上下文可以动态地改变其持有的策略对象,从而改变其行为。
4. 更好的复用性:每个策略都是一个独立可复用的单元。
缺点:
1. 类数量增多:每个策略都需要一个单独的类,如果策略很多,会导致类爆炸。
2. 客户端必须了解所有策略:客户端需要知道有哪些策略,并自行决定在何时使用哪一个策略,这增加了客户端的复杂性。(当然,也可以结合工厂模式来隐藏具体策略类,简化客户端)。
---
### 6. 适用场景
1. 当一个系统需要动态地在几种算法中选择一种时。
2. 如果一个对象有很多行为,而这些行为使用多重的条件语句来实现。此时可将这些行为封装到不同的策略类中。
3. 当一个类中定义了多种行为,并且这些行为以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中,以代替这些条件语句。
4. 当你想对客户端隐藏算法的复杂实现细节时。
### 7. 与其他模式的比较
* 策略模式 vs. 状态模式(State Pattern)
* 意图不同:策略模式关注于为同一个行为提供多种可替换的算法;状态模式关注于对象的行为如何根据其内部状态的改变而改变。
* 行为改变者:策略模式中,通常由客户端决定使用哪个策略;状态模式中,状态的转换通常由上下文或状态对象自身来管理。
* 关系:策略模式中,上下文“拥有”一个策略;状态模式中,上下文“是”一个状态。
* 策略模式 vs. 工厂模式(Factory Pattern)
* 它们经常结合使用。客户端可以使用工厂模式来创建所需的具体策略对象,然后将其注入到上下文中。这样可以隐藏具体策略类的创建细节,降低客户端的复杂性。
* 策略模式 vs. 模板方法模式(Template Method Pattern)
* 实现方式不同:策略模式使用组合/聚合关系,在运行时动态选择算法;模板方法模式使用继承关系,通过子类重写父类的部分步骤来改变算法行为。
* 粒度不同:策略模式改变的是整个算法;模板方法模式改变的是算法中的某几个特定步骤。
希望这个详细的解释能帮助你完全理解策略模式!