策略模式(Strategy Pattern)是一种行为型设计模式。
它定义了一系列算法,将每个算法都封装起来,并且使它们之间可以替换。策略模式让算法的变化独立于使用算法的客户端,即让算法变化不会影响到使用算法的客户端。
这种模式的目的是在运行时根根据不同的情况选择不同的算法或行为。
策略模式将一个算法的行为与其它算法分离开来,使它们可以独立地变化和替换。

角色(要素):

  1. 策略接口(Strategy):定义所有支持的算法的公共接口。Context 使用这个接口来调用具体某个策略定义的算法。
  2. 具体策略(Concrete Strategy):实现 Strategy 接口的具体算法。
  3. 上下文(Context):维护一个对 Strategy 对象的引用,并且负责调用具体策略的算法。

优点:

  1. 可以在运行时动态地改变算法或行为,使得系统更加灵活、可扩展和可维护。
  2. 遵循开闭原则,即对扩展开放,对修改关闭。增加新的策略算法不需要修改原有的代码,只需要增加新的策略类即可。
  3. 复用性好,可以将相同的算法封装到一个公共的策略类中。
  4. 策略模式将算法和对象分离开来,符合单一职责原则和高内聚低耦合的设计原则。

缺点:

  1. 策略模式增加了系统中类的数量,增加了系统的复杂度。
  2. 客户端必须知道所有的策略类,所有策略类都需要对外暴露,增加了客户端的负担同时也不符合迪米特原则。

常见使用场景

  1. 多个类只有在算法或行为上稍有不同的场景。
  2. 当一个系统中有多个算法或行为时,可以考虑使用策略模式。
  3. 当需要动态地在运行时根据不同的情况选择不同的算法或行为时,可以考虑使用策略模式。
  4. 当一个类有多个行为或算法,并且这些行为或算法经常发生变化时,可以考虑使用策略模式。

代码实现

// 1. 定义一个策略接口类,该类定义了所有策略类都必须实现的方法。
class Strategy {
public:
    virtual void execute() = 0;
    virtual ~Strategy() {}
};

// 2. 实现多个具体的策略类,这些类实现了策略接口中定义的方法,进行不同的算法处理。
class ConcreteStrategyA : public Strategy {
public:
    void execute() override {
        cout << "Executing strategy A" << endl;
    }
};

class ConcreteStrategyB : public Strategy {
public:
    void execute() override {
        cout << "Executing strategy B" << endl;
    }
};

class ConcreteStrategyC : public Strategy {
public:
    void execute() override {
        cout << "Executing strategy C" << endl;
    }
};

// 3. 定义一个环境类,该类持有一个策略对象,并且在运行时可以根据需要更改策略对象。
class Context {
private:
    Strategy* strategy_;

public:
    Context(Strategy* strategy = nullptr) : strategy_(strategy) {}

    void set_strategy(Strategy* strategy) {
        strategy_ = strategy;
    }

    void execute_strategy() {
        if (strategy_) {
            strategy_->execute();
        }
    }

    ~Context() {
        delete strategy_;
    }
};

// 4. 在客户端中,创建不同的策略对象,并且将它们传递给环境对象,从而触发不同的算法处理。
int main() {
    Strategy* strategyA = new ConcreteStrategyA();
    Strategy* strategyB = new ConcreteStrategyB();
    Strategy* strategyC = new ConcreteStrategyC();

    Context context1(strategyA);
    context1.execute_strategy();
    context1.set_strategy(strategyB);
    context1.execute_strategy();
    context1.set_strategy(strategyC);
    context1.execute_strategy();

    Context context2(strategyB);
    context2.execute_strategy();
    context2.set_strategy(strategyC);
    context2.execute_strategy();

    delete strategyA;
    delete strategyB;
    delete strategyC;
    return 0;
}

策略模式的常见问题:

  1. 类爆炸问题:策略模式在定义算法时需要为每个算法定义一个具体的策略类,如果算法数量很多,那么就会有大量的策略类,导致类的数量急剧增加,从而使得代码难以维护和扩展。
  2. 策略选择问题:策略模式的核心在于算法的动态选择,但是如果选择算法的逻辑过于复杂,就会导致代码可读性差、难以维护和扩展。
  3. 策略的共享问题:策略模式中的算法通常是独立的,但是如果它们之间存在共享的状态,那么就需要在不同的策略类之间传递这些状态,从而增加了代码的复杂度。
  4. 策略的变化问题:策略模式通常是为了应对算法的变化而设计的,但是如果策略本身发生变化,那么就需要修改所有使用该策略的客户端代码,这会导致代码的脆弱性和可维护性降低。

针对这些问题,我们可以采取一些解决方案来降低它们的影响:

  1. 使用组合或委托来减少策略类的数量,从而避免类爆炸问题。
  2. 使用工厂模式来封装策略的选择逻辑,从而让客户端代码更加简洁和易读。
  3. 采用享元模式来共享策略之间的状态,从而避免在不同的策略类之间传递状态的问题。
  4. 将策略的变化隔离到一个单独的模块中,从而避免对客户端代码的影响。
05-10 02:08