策略
意图
策略是一种行为设计模式,它允许您定义一系列算法,将每个算法放入单独的类中,并使它们的对象可互换。

问题
有一天,你决定为休闲旅行者创建一个导航应用程序。这款应用程序以一张漂亮的地图为中心,可以帮助用户在任何城市快速定位。
该应用程序最受欢迎的功能之一是自动路线规划。用户应该能够输入一个地址,并在地图上看到到该目的地的最快路线。
该应用程序的第一版只能在道路上建立路线。驾车旅行的人都欣喜若狂。但显然,不是每个人都喜欢在度假时开车。所以在下一次更新中,你添加了一个构建步行路线的选项。在那之后,你又增加了另一个选项,让人们在他们的路线上使用公共交通工具。
然而,这仅仅是个开始。后来你计划增加骑行路线建设。甚至在后来,另一种选择是修建贯穿城市所有旅游景点的路线。

导航器的代码变得臃肿。
虽然从商业角度来看,这款应用是成功的,但它的技术部分让你很头疼。每次添加新的路由算法时,导航器的主类的大小都会增加一倍。在某种程度上,这只野兽变得难以维持。
任何一种算法的改变,无论是简单的错误修复还是街头得分的轻微调整,都会影响到整个班级,增加了在已经工作的代码中产生错误的机会。
此外,团队合作变得效率低下。您的团队成员是在成功发布之后被录用的,他们抱怨说他们花了太多时间来解决合并冲突。实现一个新特性需要您更改相同的大型类,这会与其他人生成的代码产生冲突。
解决方案
Strategy模式建议您选择一个以许多不同方式执行特定任务的类,并将所有这些算法提取到称为策略.
最初的类,称为上下文,必须有一个字段用于存储对其中一个策略的引用。上下文将工作委托给一个链接的策略对象,而不是自己执行它。
上下文并不负责为作业选择合适的算法。相反,客户机将所需的策略传递给上下文。事实上,语境对策略的了解并不多。它通过相同的通用接口处理所有策略,该接口只公开一个方法来触发所选策略中封装的算法。
通过这种方式,上下文独立于具体的策略,因此您可以添加新的算法或修改现有算法,而无需更改上下文或其他策略的代码。

路线规划策略。
在我们的导航应用程序中,每个路由算法都可以提取到它自己的类buildRoute
方法。该方法接受起点和目的地,并返回路由检查点的集合。
即使给定相同的参数,每个路由类可能会构建不同的路由,但主导航器类并不真正关心选择哪种算法,因为它的主要工作是在地图上呈现一组检查点。该类有一个用于切换活动路由策略的方法,因此它的客户端(例如用户界面中的按钮)可以用另一个行为替换当前选择的路由行为。
真实的模拟

去机场的各种策略。
想象一下你要去机场。你可以乘公共汽车,叫出租车,或者骑自行车。这些是你的交通策略。你可以根据预算或时间限制等因素选择一种策略。
结构

的上下文维护对具体策略之一的引用,并仅通过策略接口与此对象通信。
的策略接口是所有具体策略所共有的。它声明了上下文用来执行策略的方法。
具体的策略实现上下文使用的算法的不同变体。
每当上下文需要运行算法时,它都会调用链接策略对象上的执行方法。上下文不知道它使用什么类型的策略,也不知道算法是如何执行的。
的客户端创建一个特定的策略对象并将其传递给上下文。上下文公开了一个setter,允许客户端在运行时替换与上下文关联的策略。
伪代码
在本例中,上下文使用了multiple策略执行各种算术运算。
//策略接口声明了某些算法的所有支持版本的通用操作。上下文使用这个//接口调用具体//策略定义的算法。interface Strategy is method execute(a, b) //具体策略在遵循基本策略接口时实现算法。接口使它们在上下文中//可互换。类ConcreteStrategyAdd实现的策略是方法execute(a, b)返回a + b类ConcreteStrategySubtract实现的策略是方法execute(a, b)返回a - b类ConcreteStrategyMultiply实现的策略是方法execute(a, b)返回a * b //上下文定义了客户感兴趣的接口。Context维护一个对策略//对象的引用。上下文不知道//策略的具体类。它应该通过// strategy接口与所有策略一起工作。通常情况下,上下文通过//构造函数接受一个策略,并提供一个setter,以便//策略可以在运行时切换。方法setStrategy(策略策略)是这样的。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.
适用性
当您希望在对象中使用算法的不同变体,并能够在运行时从一种算法切换到另一种算法时,请使用Strategy模式。
Strategy模式允许您通过将对象与不同的子对象相关联来间接地改变对象在运行时的行为,这些子对象可以以不同的方式执行特定的子任务。
当您有很多相似的类,只是它们执行某些行为的方式不同时,请使用策略。
Strategy模式允许您将不同的行为提取到单独的类层次结构中,并将原始类合并为一个,从而减少重复代码。
使用模式将类的业务逻辑与算法的实现细节隔离开来,这些算法的实现细节在该逻辑的上下文中可能不那么重要。
Strategy模式允许您将代码、内部数据和各种算法的依赖关系与其余代码隔离开来。各种客户端都有一个简单的接口来执行算法并在运行时进行切换。
当你的类有大量的条件语句,在相同算法的不同变体之间切换时,使用这个模式。
Strategy模式允许您通过将所有算法提取到单独的类(所有类都实现相同的接口)来消除这样的条件。原始对象将执行委托给其中一个对象,而不是实现算法的所有变体。
如何实施
在上下文类中,确定一个容易频繁更改的算法。它也可能是一个大型条件,在运行时选择并执行同一算法的变体。
声明对算法的所有变体通用的策略接口。
将所有算法逐一提取到各自的类中。它们都应该实现策略接口。
在上下文类中,添加一个字段,用于存储对策略对象的引用。提供一个setter来替换该字段的值。上下文只能通过策略接口与策略对象一起工作。上下文可以定义一个允许策略访问其数据的接口。
上下文的客户端必须将上下文与合适的策略相关联,该策略必须与上下文执行其主要工作的方式相匹配。
利与弊
-
- 您可以在运行时交换对象内部使用的算法。
-
- 可以将算法的实现细节与使用算法的代码隔离开来。
-
- 你可以用组合代替继承。
-
- 打开/关闭原则.你可以在不改变环境的情况下引入新的策略。
-
- 如果您只有几个算法,而且它们很少改变,那么就没有真正的理由让程序过于复杂,让模式中出现新的类和接口。
-
- 客户必须了解策略之间的差异,以便能够选择合适的策略。
-
- 许多现代编程语言都具有函数类型支持,允许您在一组匿名函数中实现不同版本的算法。然后,您就可以像使用策略对象一样使用这些函数,但不会因为额外的类和接口而使代码膨胀。