设计二十三式之策略模式
01 意图
策略是一种行为设计模式,它允许您定义一系列算法,将它们中的每一个放入一个单独的类中,并使它们的对象可互换。
02 问题
有一天,您决定为休闲旅行者创建一个导航应用程序。该应用程序以精美的地图为中心,可帮助用户在任何城市快速定位自己。
该应用程序最需要的功能之一是自动路线规划。用户应该能够输入地址并在地图上查看到该目的地的最快路线。
该应用程序的第一个版本只能在道路上构建路线。驾车出行的人都喜出望外。但显然,并不是每个人都喜欢在假期开车。因此,在下一次更新中,您添加了一个构建步行路线的选项。在那之后,您添加了另一个选项,让人们在他们的路线中使用公共交通工具。
然而,这仅仅是个开始。后来您计划为骑自行车的人添加路线建设。甚至在以后,另一种选择是建立穿越城市所有旅游景点的路线。
虽然从业务角度来看,该应用程序是成功的,但技术部分却让您头疼不已。每次添加新的路由算法时,导航器的主类都会增加一倍。在某些时候,野兽变得难以维持。
对其中一种算法的任何更改,无论是简单的错误修复还是街道分数的轻微调整,都会影响整个类,增加在已经工作的代码中创建错误的机会。
此外,团队合作变得低效。您的团队成员在成功发布后立即被雇用,他们抱怨他们花费太多时间来解决合并冲突。实现一个新特性需要你更改同一个巨大的类,与其他人生成的代码冲突。
03 解决方案
策略模式建议您采用一个以多种不同方式执行特定操作的类,并将所有这些算法提取到称为策略的单独类中。
称为context的原始类必须有一个字段用于存储对其中一种策略的引用。上下文将工作委托给链接的策略对象,而不是自行执行。
上下文不负责为工作选择合适的算法。相反,客户端将所需的策略传递给上下文。实际上,上下文对策略了解不多。它通过相同的通用接口与所有策略一起工作,该接口仅公开一个用于触发封装在所选策略中的算法的方法。
这样,上下文变得独立于具体策略,因此您可以添加新算法或修改现有算法,而无需更改上下文代码或其他策略。
在我们的导航应用程序中,每个路由算法都可以通过一个buildRoute
方法提取到自己的类中。该方法接受起点和终点,并返回路线检查点的集合。
即使给定相同的参数,每个路由类也可能构建不同的路由,主导航器类并不真正关心选择哪种算法,因为它的主要工作是在地图上呈现一组检查点。该类具有切换活动路由策略的方法,因此其客户端(例如用户界面中的按钮)可以将当前选择的路由行为替换为另一个。
04 举个栗子
想象一下,你必须去机场。您可以搭乘公共汽车、叫出租车或骑自行车。
这些是您的运输策略。您可以根据预算或时间限制等因素选择其中一种策略。
05 结构实现
在此示例中,上下文使用多种策略来执行各种算术运算。
// The strategy interface declares operations common to all
// supported versions of some algorithm. The context uses this
// interface to call the algorithm defined by the concrete
// strategies.
interface Strategy is
method execute(a, b)
// Concrete strategies implement the algorithm while following
// the base strategy interface. The interface makes them
// interchangeable in the context.
class ConcreteStrategyAdd implements Strategy is
method execute(a, b) is
return a + b
class ConcreteStrategySubtract implements Strategy is
method execute(a, b) is
return a - b
class ConcreteStrategyMultiply implements Strategy is
method execute(a, b) is
return a * b
// The context defines the interface of interest to clients.
class Context is
// The context maintains a reference to one of the strategy
// objects. The context doesn't know the concrete class of a
// strategy. It should work with all strategies via the
// strategy interface.
private strategy: Strategy
// Usually the context accepts a strategy through the
// constructor, and also provides a setter so that the
// strategy can be switched at runtime.
method setStrategy(Strategy strategy) is
this.strategy = strategy
// The context delegates some work to the strategy object
// instead of implementing multiple versions of the
// algorithm on its own.
method executeStrategy(int a, int b) is
return strategy.execute(a, b)
// The client code picks a concrete strategy and passes it to
// the context. The client should be aware of the differences
// between strategies in order to make the right choice.
class ExampleApplication is
method main() is
Create context object.
Read first number.
Read last number.
Read the desired action from user input.
if (action == addition) then
context.setStrategy(new ConcreteStrategyAdd())
if (action == subtraction) then
context.setStrategy(new ConcreteStrategySubtract())
if (action == multiplication) then
context.setStrategy(new ConcreteStrategyMultiply())
result = context.executeStrategy(First number, Second number)
Print result.
06 适用场景
当您想在对象中使用算法的不同变体并能够在运行时从一种算法切换到另一种算法时,请使用策略模式。
策略模式允许您通过将对象与可以以不同方式执行特定子任务的不同子对象相关联来间接改变对象在运行时的行为。
当您有许多相似的类,它们只是在执行某些行为的方式上有所不同时,请使用该策略。
策略模式让您可以将不同的行为提取到单独的类层次结构中,并将原始类组合成一个类,从而减少重复代码。
使用该模式将类的业务逻辑与在该逻辑的上下文中可能不那么重要的算法的实现细节隔离开来。
策略模式允许您将各种算法的代码、内部数据和依赖项与其余代码隔离开来。各种客户端获得一个简单的接口来执行算法并在运行时切换它们。
当您的类有一个在同一算法的不同变体之间切换的大量条件运算符时,请使用该模式。
策略模式允许您通过将所有算法提取到单独的类中来消除这种条件,所有这些都实现相同的接口。原始对象将执行委托给这些对象之一,而不是实现算法的所有变体。
07 如何实施
在上下文类中,确定一种易于频繁更改的算法。它也可能是在运行时选择并执行同一算法的变体的大量条件。
声明算法所有变体通用的策略接口。
一个一个地,将所有算法提取到自己的类中。他们都应该实现策略接口。
在上下文类中,添加一个用于存储对策略对象的引用的字段。提供用于替换该字段值的设置器。上下文应仅通过策略接口与策略对象一起使用。上下文可以定义让策略访问其数据的接口。
上下文的客户必须将其与合适的策略相关联,该策略与他们期望上下文执行其主要工作的方式相匹配。
08 优缺点