Skip to content

策略模式

策略模式(Strategy pattern)是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。

假如你需要前往机场。 你可以选择乘坐公共汽车、 预约出租车或骑自行车。 这些就是你的出行策略。 你可以根据预算或时间等因素来选择其中一种策略。

策略模式结构

  • 上下文 (Context) 维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。

  • 策略 (Strategy) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。

  • 具体策略 (Concrete Strategies) 实现了上下文所用算法的各种不同变体。

  • 当上下文需要运行算法时, 它会在其已连接的策略对象上调用执行方法。 上下文不清楚其所涉及的策略类型与算法的执行方式。

  • 客户端 (Client) 会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。

应用场景

  1. 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。

    策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。

  2. 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。

    策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。

  3. 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。

    策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。

  4. 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。

    策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。

代码示例

ts
/**
 * Context定义了客户端感兴趣的接口。 
 */
class Context {
    /**
     * Context维护了一个Strategy对象的引用。上下文不知道策略的具体类。它应该通过策略界面与所有策略一起工作。
     */
    private strategy: Strategy;

    /**
     * 通常,Context通过构造函数接受一个策略,但也提供了一个setter来在运行时更改它。
     */
    constructor(strategy: Strategy) {
        this.strategy = strategy;
    }

    /**
     * 通常,Context允许在运行时替换Strategy对象。
     */
    public setStrategy(strategy: Strategy) {
        this.strategy = strategy;
    }

    /**
     * Context将一些工作委托给Strategy对象,而不是自己实现多个版本的算法。
     */
    public doSomeBusinessLogic(): void {
        // ...

        console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)');
        const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']);
        console.log(result.join(','));

        // ...
    }
}

/**
 * Strategy接口声明了某些算法所有支持版本的通用操作。
 * Context使用这个接口来调用具体策略定义的算法。
 */
interface Strategy {
    doAlgorithm(data: string[]): string[];
}

/**
 * 具体策略在遵循基本策略接口的同时实现算法。该接口使它们在上下文中可以互换。
 */
class ConcreteStrategyA implements Strategy {
    public doAlgorithm(data: string[]): string[] {
        return data.sort();
    }
}

class ConcreteStrategyB implements Strategy {
    public doAlgorithm(data: string[]): string[] {
        return data.reverse();
    }
}

/**
 * 客户端代码选择一个具体的策略并传递给上下文。
 * 为了做出正确的选择,客户应该了解不同策略之间的差异。
 */
const context = new Context(new ConcreteStrategyA());
console.log('Client: Strategy is set to normal sorting.');
context.doSomeBusinessLogic();

console.log('');

console.log('Client: Strategy is set to reverse sorting.');
context.setStrategy(new ConcreteStrategyB());
context.doSomeBusinessLogic();

output

ts
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e

Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a