基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

设计模式--策略模式 (Strategy)

知识点图片

策略模式将一系列算法分别封装,使其可相互替换。它让算法的变化独立于使用者,有效避免了代码中大量的 if-else 判断。

我们来详细、清晰地讲解一下设计模式中的策略模式(Strategy Pattern)

1. 什么是策略模式?

一句话定义:
策略模式定义了一系列的算法,并将每一个算法封装起来,使它们可以互相替换。此模式让算法的变化独立于使用算法的客户。

通俗理解:
想象一下你去某个地方旅游,你可以选择多种交通方式:

  • 坐飞机(速度快,价格高)
  • 坐火车(速度适中,价格适中)
  • 自己开车(灵活,但可能累)

对于“去旅游”这个行为(我们称之为上下文 Context),具体采用哪种交通方式(我们称之为策略 Strategy),是可以随时切换的。你今天可以决定坐飞机去,明天可以决定开车回来。

策略模式做的就是这件事:它将这些交通方式(算法)各自封装成独立的类,然后让你的“旅行计划”(上下文)持有一个交通方式的引用。当你需要改变出行方式时,只需更换这个引用的具体对象即可,而无需修改“旅行计划”本身的代码。


2. 解决什么问题?

策略模式主要解决在一个类中存在大量 if-elseswitch-case 语句的问题。当一个操作有多种不同的实现方式,并且你希望在运行时根据不同情况选择其中一种时,通常会写出这样的代码:

反例(不使用策略模式):

java
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("不支持的支付方式");
        }
    }
}

这种写法的缺点:

  1. 违反开放/封闭原则:如果需要增加一种新的支付方式(比如银联支付),就必须修改 processPayment 方法的内部代码,这增加了引入错误的风险。
  2. 代码臃肿,难以维护:随着支付方式的增多,if-else 结构会变得越来越长,代码可读性和可维护性急剧下降。
  3. 复用性差:支付逻辑被硬编码在 PaymentService 类中,无法在其他地方复用。

策略模式通过将这些“分支逻辑”提取出来,封装到各自独立的策略类中,从而解决了上述问题。


3. 策略模式的结构

策略模式包含三个核心角色:

  1. Context (上下文)

    • 它持有一个 Strategy 接口的引用。
    • 它不关心具体算法的实现细节,只负责在需要时调用策略对象的方法。
    • 它通常提供一个方法来让客户端设置(或切换)具体的策略对象。
  2. Strategy (抽象策略)

    • 通常是一个接口或抽象类。
    • 它定义了所有支持的算法的公共接口。上下文类通过这个接口调用具体的策略。
  3. ConcreteStrategy (具体策略)

    • 实现了 Strategy 接口。
    • 封装了具体的算法或行为。
    • 每个具体策略类都代表一种算法实现。

UML 结构图:

plaintext
+----------------+      +------------------+
|    Context     |<>--->|    IStrategy     |
+----------------+      +------------------+
| - strategy     |      | + doAlgorithm()  |
| + setStrategy()|      +------------------+
| + execute()    |               ^
+----------------+               |
        |                        |
        +------------------------+
        |                        |
+------------------+      +------------------+
| ConcreteStrategyA|      | ConcreteStrategyB|
+------------------+      +------------------+
| + doAlgorithm()  |      | + doAlgorithm()  |
+------------------+      +------------------+
  • Context 通过组合关系持有一个 IStrategy 对象。
  • ConcreteStrategyAConcreteStrategyB 都实现了 IStrategy 接口。

4. 代码示例

我们用一个商场打折的场景来重构上面的支付例子,这个场景更经典。假设一个订单有多种优惠策略:无优惠、9折优惠、满300减50。

第1步:定义抽象策略 (Strategy)

创建一个接口,定义计算价格的通用方法。

java
// 1. 抽象策略接口: DiscountStrategy
interface DiscountStrategy {
    double calculateDiscount(double price);
}

第2步:创建具体策略 (ConcreteStrategy)

为每一种优惠方式创建一个具体的实现类。

java
// 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 (订单) 类就是我们的上下文,它需要根据不同的策略来计算最终价格。

java
// 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步:客户端调用

客户端根据需要创建和设置不同的策略。

java
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

plaintext

---

### 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)
    *   实现方式不同:策略模式使用组合/聚合关系,在运行时动态选择算法;模板方法模式使用继承关系,通过子类重写父类的部分步骤来改变算法行为。
    *   粒度不同:策略模式改变的是整个算法;模板方法模式改变的是算法中的某几个特定步骤。

希望这个详细的解释能帮助你完全理解策略模式!
00:00
00:00