设计模式:策略模式

意图

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

 解决方案

策略模式建议找出负责用许多不同方式完成特定任务的类 然后将其中的算法抽取到一组被称为策略的独立类中

名为上下文的原始类必须包含一个成员变量来存储对于每种策略的引用 上下文并不执行任务 而是将工作委派给已连接的策略对象

上下文不负责选择符合任务需要的算法——客户端会将所需策略传递给上下文 实际上 上下文并不十分了解策略 它会通过同样的通用接口与所有策略进行交互 而该接口只需暴露一个方法来触发所选策略中封装的算法即可

因此 上下文可独立于具体策略 这样你就可在不修改上下文代码或其他策略的情况下添加新算法或修改已有算法了

策略模式结构

策略设计模式的结构

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

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

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

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

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

策略模式适合应用场景

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

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

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

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

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

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

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

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

策略模式优缺点

优点:

  •  你可以在运行时切换对象内的算法
  •  你可以将算法的实现和使用算法的代码隔离开来
  •  你可以使用组合来代替继承
  •  开闭原则 你无需对上下文进行修改就能够引入新的策略

缺点:

  •  如果你的算法极少发生改变 那么没有任何理由引入新的类和接口 使用该模式只会让程序过于复杂
  •  客户端必须知晓策略间的不同——它需要选择合适的策略
  •  许多现代编程语言支持函数类型功能 允许你在一组匿名函数中实现不同版本的算法 这样 你使用这些函数的方式就和使用策略对象时完全相同 无需借助额外的类和接口来保持代码简洁

与其他模式的关系

  • 桥接模式 状态模式策略模式 在某种程度上包括适配器模式 模式的接口非常相似 实际上 它们都基于组合模式——即将工作委派给其他对象 不过也各自解决了不同的问题 模式并不只是以特定方式组织代码的配方 你还可以使用它们来和其他开发者讨论模式所解决的问题

  • 命令模式策略看上去很像 因为两者都能通过某些行为来参数化对象 但是 它们的意图有非常大的不同

    • 你可以使用命令来将任何操作转换为对象 操作的参数将成为对象的成员变量 你可以通过转换来延迟操作的执行 将操作放入队列 保存历史命令或者向远程服务发送命令等

    • 另一方面 策略通常可用于描述完成某件事的不同方式 让你能够在同一个上下文类中切换算法

  • 装饰模式可让你更改对象的外表 策略则让你能够改变其本质

  • 模板方法模式基于继承机制 它允许你通过扩展子类中的部分内容来改变部分算法 策略基于组合机制 你可以通过对相应行为提供不同的策略来改变对象的部分行为 模板方法在类层次上运作 因此它是静态的 策略在对象层次上运作 因此允许在运行时切换行为

  • 状态可被视为策略的扩展 两者都基于组合机制 它们都通过将部分工作委派给 帮手 对象来改变其在不同情景下的行为 策略使得这些对象相互之间完全独立 它们不知道其他对象的存在 状态模式没有限制具体状态之间的依赖 且允许它们自行改变在不同情景下的状态

在 Java 中使用模式

使用示例 策略模式在 Java 代码中很常见 它经常在各种框架中使用 能在不扩展类的情况下向用户提供改变其行为的方式

Java 8 开始支持 lambda 方法 它可作为一种替代策略模式的简单方式

这里有一些核心 Java 程序库中策略模式的示例

示例代码

上下文 Context

public class LogFactory {
  public static Logger getConsoleLogger() {
    return new ConsoleLogger();
  }

  public static Logger getFileLogger() {
    return new FileLogger(new File("app.log"));
  }
}

策略 Strategy

public interface Logger {
    void write(String message);
}

 具体策略 Concrete Strategies

public class ConsoleLogger implements Logger {
    @Override
    public void write(final String message) {
        System.out.println(message);
    }
}
public class FileLogger implements Logger {
    private final File file;

    public FileLogger(final File file) {
        this.file = file;
    }

    @Override
    public void write(final String message) {
        System.out.println("Write log to file: " + file);
        System.out.println(message);
    }
}

客户端 Client 

 

public class Client {
    public static void main(String[] args) {
        Logger logger = LogFactory.getConsoleLogger();
        logger.write("Hello!");

        Logger fileLogger = LogFactory.getFileLogger();
        fileLogger.write("Test");
    }
}