意图
Strategy是行为模式的一种,让你定义一组算法,各自封装,并且他们可替换。Strategy让这些算法独立与使用他们的客户端。
问题
一天你决定写一个给驴友使用的导航应用。这个应用以漂亮的地图为中心,允许用户在任何城市快速的定位。应用最大的特点是能够自动规划路线,所以你决定特别关注这点。用户可以输入一个期望的目的地,能够快速在屏幕上画出路线。
第一个版本的应用只能规划道路上的路线,适合汽车旅客。但显然不是所有的人都喜欢在休假时开车。所以下一次更新,你添加了规划步行路线的选项。之后,你又增加了一个选项,允许用户规划基于公共交通的路线。
但这仅是一个开始。最近的版本中你计划增加一个可以规划自行车路线的特性。之后,根据沿途景点规划路线成为可选项。
这款应用的业务是成功的,但是技术部分让你头疼不已。
每次增加一个新的路线算法,Map
类的大小就会增长。至此,应用开始变得难以维护。
任何搜索算法的改变,比如修复一个BUG或者微调算法行为,都会影响整个类,增加了让已存在代码出错的风险。
最终,团队合作变得低效。你的队友在你成功发布一次之后都会抱怨要花费大量的时间在处理代码合并冲突上,因为它们都和一个大类相关联。
解决
策略模式建议采使用一个以许多不同方式做重要事情的类,并将这些算法分别提取到单独的被称为策略的类中。
源类被称为context,它有一个字段来接受存储所有策略中的一种。上下文把工作委托给关联的策略,而不是自己去执行。
上下文没有选择合适算法的职责,客户端负责传递一个适当的策略到上下文中。
事实上,上下文不知道策略的细节。上下文只通过策略基本接口暴漏出来的方法和它通信。这是的上下文独立与策略,允许你在不修改上下文或者其他策略的情况下添加新的策略。
在我们导航应用中,每个路线算法都将被抽离到它们自己对应的只有一个buildRoute
方法的类中。这个方法接受出发地和目的地,返回路线检查点的集合。
甚至每个路线类在相同的参数下会给出不同的路线。Map
类不需要在关心那个策略被选中,因为它唯一的工作就是把检查点渲染到地图上。
这个Map
将会提供出一个方法来切换路线策略,这样客户端就可以提供出一个按钮给用户来修改当前规划路线的方式。
真实世界的类比
运输
你必须到机场去。你可以坐巴士,打的或者骑单车。出行方式就是策略。你根据上下文来选择一个策略,比如预算或者时间限制。
结构
Context存储一个Concrete Strategy对象,但是只通过通用的Strategy接口和其协作。上下文应当报漏出一个setter方法让其他对象可以替换关联的策略对象。
Strategy为所有的策略声明了一个通用接口。这个接口让具体的策略在上下文中是可替换的。
Concrete Strategy实现了不同的算法,旨在以不同的方式完成相同的工作。
上下文只在它需要的时候去调用策略来执行任务。但它并不知道到底是那个策略在执行。
Client会根据不同的场景选择不同的策略。他们可以在运行时根据需要来配置上下文的策略。
伪代码
在这个例子中,上下文使用策略来进行不同的算数运算。
1 | // Common interface for all strategies. |
适用性
当你有一个对象需要以许多不同的方式来做相同的任务时。
策略模式允许你在运行时通过提供不同的子对象(实际处理工作的对象)来修改对象的行为。
当你有许多相似的类,它们以不同的方式执行一些行为。
策略模式允许你将所有这些类的行为抽取到单独的类层次结构中,从而使原始类的行为可以自定义。
当你不想把算法实现的细节暴露给其他类时。
策略模式通过把代码,内部数据和算法的依赖关系提取到自己的类中来隔离其他对象。
一个算法通过大量条件操作被选择执行。背个条件分支代表不同的算法。
策略允许你通过抽离每个算法到自己的类中来分解条件,这些类都遵循一个相同的接口。上下文把工作委托给这些对象,而不是自己实现这些行为。
如何实现
确定客户希望通过“flex ponit”访问的算法。
为这个算法所有的实现声明通用的接口。
挨个把这些算法抽离到它们自己的类中。它们都应当遵循通用的Strategy接口。
给Context类增加一个字段来关联当前使用的策略,并提供一个setter方法用来改变策略。上下文应当只通过Strategy接口和策略协作。
当客户端需要上下文来以某种方式工作的时候,必须提供一个合适策略对象。
优点
允许在运行时热替换算法。
对其他类隔离代码和算法数据。
用委托替代继承。
符合开闭原则。
缺点
通过创建多个额外类增加代码复杂度。
客户端必须关心策略间的差异以便选择正确的策略。
和其他模式的关系
State,Strategy,Bridge(和某种程度上的Adapter)都有相似的解决结构。它们都是共享“handle/body”句柄。它们的意图不一样,所以他们用来解决不同的问题。
Command和Strategy很相似,因为他们都是把一些行为参数化到上下文。Command可以用爱把任何操作转换到一个对象中。可选的参数变成了那个对象的字段。这个转换允许延迟或者远程执行,存储命令历史等。
另一方面,Strategy模式通常用来描述处理相同事情的不同方式。它可以帮助我们让这些算法在一个单独的上下文类中是可替换的。
Decorator可以让你改变一个对象的皮肤(外部表象)。Strategy让你改变胆子(内部实现)。
Template Method使用继承来改变算法(子类扩展父类的某些方法)。Strategy通过把工作委托给可替换的策略对象来改变对象的行为。模版方法模式工作在类这一层面。策略模式允许你改变个性化对象的行为。
State可以看作是Strategy模式的一种扩展。这两种模式都采用组合的方式来改变主对象的行为,主对象委托工作给这些帮助对象。Strategy模式让这些对象完全独立。State模式允许状态对象改变上下文对象的当前状态,他们之间是相互依存的。
小结
策略模式是行为模式的一种,他把一系列行为分解到不同的对象中,使得他们在源上下文对象中可以互换。
源对象被叫做上下文,他会持有一个策略对象的引用,并把任务委托给策略执行。为了改变上下文处理工作的方式,其他对象可以修改当前上下文中的策略对象。