设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
策略模式
> 策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。 ![策略设计模式](/media/202203/2022-03-16_1402440.41488926067831156.png) 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。 ## 问题 一天, 你打算为游客们创建一款导游程序。 该程序的核心功能是提供美观的地图, 以帮助用户在任何城市中快速定位。 用户期待的程序新功能是自动路线规划: 他们希望输入地址后就能在地图上看到前往目的地的最快路线。 程序的首个版本只能规划公路路线。 驾车旅行的人们对此非常满意。 但很显然, 并非所有人都会在度假时开车。 因此你在下次更新时添加了规划步行路线的功能。 此后, 你又添加了规划公共交通路线的功能。 而这只是个开始。 不久后, 你又要为骑行者规划路线。 又过了一段时间, 你又要为游览城市中的所有景点规划路线。 尽管从商业角度来看, 这款应用非常成功, 但其技术部分却让你非常头疼: 每次添加新的路线规划算法后, 导游应用中主要类的体积就会增加一倍。 终于在某个时候, 你觉得自己没法继续维护这堆代码了。 无论是修复简单缺陷还是微调街道权重, 对某个算法进行任何修改都会影响整个类, 从而增加在已有正常运行代码中引入错误的风险。 此外, 团队合作将变得低效。 如果你在应用成功发布后招募了团队成员, 他们会抱怨在合并冲突的工作上花费了太多时间。 在实现新功能的过程中, 你的团队需要修改同一个巨大的类, 这样他们所编写的代码相互之间就可能会出现冲突。 ## 解决方案 策略模式建议找出负责用许多不同方式完成特定任务的类, 然后将其中的算法抽取到一组被称为策略的独立类中。 名为上下文的原始类必须包含一个成员变量来存储对于每种策略的引用。 **上下文并不执行任务, 而是将工作委派给已连接的策略对象**。 上下文不负责选择符合任务需要的算法——**客户端会将所需策略传递给上下文**。 实际上, 上下文并不十分了解策略, 它会通过同样的通用接口与所有策略进行交互, 而该接口只需暴露一个方法来触发所选策略中封装的算法即可。 因此, 上下文可独立于具体策略。 这样你就可在不修改上下文代码或其他策略的情况下添加新算法或修改已有算法了。 ![路线规划策略](/media/202203/2022-03-16_1410270.49933703504172666.png) 在导游应用中, 每个路线规划算法都可被抽取到只有一个 buildRoute 生成路线方法的独立类中。 该方法接收起点和终点作为参数, 并返回路线中途点的集合。 即使传递给每个路径规划类的参数一模一样, 其所创建的路线也可能完全不同。 主要导游类的主要工作是在地图上渲染一系列中途点, 不会在意如何选择算法。 该类中还有一个用于切换当前路径规划策略的方法, 因此客户端 (例如用户界面中的按钮) 可用其他策略替换当前选择的路径规划行为。 ## 目的 分离「策略」并使他们之间能互相快速切换。此外,这种模式是一种不错的继承替代方案(替代使用扩展抽象类的方式)。 ## 介绍 **意图:** 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。 **主要解决:** 在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。 **何时使用:** 一个系统有许多许多类,而区分它们的只是他们直接的行为。 **如何解决:** 将这些算法封装成一个一个的类,任意地替换。 **关键代码:** 实现同一个接口。 **应用实例:** 1. 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。 2. 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。 3. JAVA AWT 中的 LayoutManager。 4. 对一个对象列表进行排序,一种按照日期,一种按照 id。 5. 简化版的的单元测试:例如,在使用文件存储和内存存储之间互相切换。 **优点:** 1. 算法可以自由切换。 2. 避免使用多重条件判断。 3. 扩展性良好。 **缺点:** 1. 策略类会增多。 2. 所有策略类都需要对外暴露。 **使用场景:** 1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2. 一个系统需要动态地在几种算法中选择一种。 3. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。 **注意事项:** 如果一个系统的策略多于四个,就需要考虑使用**混合模式**,解决策略类膨胀的问题。 ## 结构 ![策略设计模式的结构](/media/202203/2022-03-16_1414390.9303957164067964.png) - 上下文 (Context) 维护指向具体策略的引用, 且**仅通过策略接口与该对象进行交流**。 - 策略 (Strategy) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。 - 具体策略 (Concrete Strategies) 实现了上下文所用算法的各种不同变体。 - 当上下文需要运行算法时, 它会在其已连接的策略对象上调用执行方法。 上下文不清楚其所涉及的策略类型与算法的执行方式。 - 客户端 (Client) 会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。 ## 实例 我们将创建一个定义活动的 *Strategy* 接口和实现了 *Strategy* 接口的实体策略类。*Context* 是一个使用了某种策略的类。 *StrategyPatternDemo* ,我们的演示类使用 *Context* 和策略对象来演示 Context 在它所配置或使用的策略改变时的行为变化。 ![策略模式的 UML 图](/media/202203/2022-03-16_1416290.20998086717992004.png) ## 伪代码 在本例中, 上下文使用了多个策略来执行不同的计算操作。 ``` // 策略接口声明了某个算法各个不同版本间所共有的操作。上下文会使用该接口来 // 调用有具体策略定义的算法。 interface Strategy is method execute(a, b) // 具体策略会在遵循策略基础接口的情况下实现算法。该接口实现了它们在上下文 // 中的互换性。 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 // 上下文定义了客户端关注的接口。 class Context is // 上下文会维护指向某个策略对象的引用。上下文不知晓策略的具体类。上下 // 文必须通过策略接口来与所有策略进行交互。 private strategy: Strategy // 上下文通常会通过构造函数来接收策略对象,同时还提供设置器以便在运行 // 时切换策略。 method setStrategy(Strategy strategy) is this.strategy = strategy // 上下文会将一些工作委派给策略对象,而不是自行实现不同版本的算法。 method executeStrategy(int a, int b) is return strategy.execute(a, b) // 客户端代码会选择具体策略并将其传递给上下文。客户端必须知晓策略之间的差 // 异,才能做出正确的选择。 class ExampleApplication is method main() is 创建上下文对象。 读取第一个数。 读取最后一个数。 从用户输入中读取期望进行的行为。 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) 打印结果。 ``` ## 应用场景 - 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式。 策略模式让你能够将对象关联至可以不同方式执行特定子任务的不同子对象, 从而以间接方式在运行时更改对象行为。 - 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式。 策略模式让你能将不同行为抽取到一个独立类层次结构中, 并将原始类组合成同一个, 从而减少重复代码。 - 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来。 策略模式让你能将各种算法的代码、 内部数据和依赖关系与其他代码隔离开来。 不同客户端可通过一个简单接口执行算法, 并能在运行时进行切换。 - 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。 策略模式将所有继承自同样接口的算法抽取到独立类中, 因此不再需要条件语句。 原始对象并不实现所有算法的变体, 而是将执行工作委派给其中的一个独立算法对象。 ## 实现方式 1. 从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符)。 2. 声明该算法所有变体的通用策略接口。 3. 将算法逐一抽取到各自的类中, 它们都必须实现策略接口。 4. 在上下文类中添加一个成员变量用于保存对于策略对象的引用。 然后提供设置器以修改该成员变量。 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据。 5. 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作。 ## 优点 - 你可以在运行时切换对象内的算法。 - 你可以将算法的实现和使用算法的代码隔离开来。 - 你可以使用组合来代替继承。 - 开闭原则。 你无需对上下文进行修改就能够引入新的策略。 ## 缺点 - 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。 - 客户端必须知晓策略间的不同——它需要选择合适的策略。 - 许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。 ## 与其他模式的关系 - 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 - 命令模式和策略看上去很像, 因为两者都能**通过某些行为来参数化对象**。 但是, 它们的意图有非常大的不同。 - 你可以使用命令来将任何操作转换为对象。 操作的参数将成为对象的成员变量。 你可以通过转换来延迟操作的执行、 将操作放入队列、 保存历史命令或者向远程服务发送命令等。 - 另一方面, 策略通常可用于描述完成某件事的不同方式, 让你能够在同一个上下文类中切换算法。 - 装饰模式可让你更改对象的外表, 策略则让你能够改变其本质。 - 模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。 - 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。 ## 示例代码 ### TS ```ts /** * The Context defines the interface of interest to clients. */ class Context { /** * @type {Strategy} The Context maintains a reference to one of the Strategy * objects. The Context does not 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, but also * provides a setter to change it at runtime. */ constructor(strategy: Strategy) { this.strategy = strategy; } /** * Usually, the Context allows replacing a Strategy object at runtime. */ public setStrategy(strategy: Strategy) { this.strategy = strategy; } /** * The Context delegates some work to the Strategy object instead of * implementing multiple versions of the algorithm on its own. */ 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(',')); // ... } } /** * The Strategy interface declares operations common to all supported versions * of some algorithm. * * The Context uses this interface to call the algorithm defined by Concrete * Strategies. */ interface Strategy { doAlgorithm(data: string[]): string[]; } /** * Concrete Strategies implement the algorithm while following the base Strategy * interface. The interface makes them interchangeable in the Context. */ class ConcreteStrategyA implements Strategy { public doAlgorithm(data: string[]): string[] { return data.sort(); } } class ConcreteStrategyB implements Strategy { public doAlgorithm(data: string[]): string[] { return data.reverse(); } } /** * 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. */ 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(); ``` 输出: ```txt 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 ```
追风者
2022年3月29日 20:18
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码