mcs调制与编码策略
如何在运行时利用多态
![](https://i-blog.csdnimg.cn/blog_migrate/e8c64690d6daf3877f421303ed3f3119.png)
面向对象设计的好处之一是对象能够共享某些行为,同时又能区别于其他行为。 通常,这是通过继承实现的-当许多子类从父类继承属性,但可以根据需要重写某些行为。 这是一种非常有用且通用的设计模式。 但是,在某些情况下,通过继承进行多态性是不合适的。 例如,考虑一下当您只需要更改单个行为而又希望对象保持不变时。 或者,当您希望对象根据某些外部(但不可预测)因素在运行时改变其行为时。 在这种情况下,继承方案可能会导致不必要的肿代码,难以维护(尤其是随着子类型数量的增加)。 但是,有一种更好的方法: 策略模式 。
通过策略实现多态
策略模式 ,也称为策略模式 ,是一种行为设计模式,使对象可以根据运行时提供的外部上下文执行某种算法(策略)。 当您拥有一个需要能够在不同时间以不同方式执行单个行为的对象时,此模式特别有用。 通过使用策略模式,您可以定义一组算法,这些算法可以在需要时动态提供给特定对象。 这种模式有很多好处,包括:将特定算法封装在自己的类中; 隔离有关如何实现算法的知识; 以及更灵活,可移动和可维护的代码。 最后一点,您可能会注意到,这些属性与遵循开放式/封闭式原则 (OCP)的代码所产生的属性相同,并且实际上,策略模式是编写符合OCP的代码的绝佳方法。
实施策略模式时,您需要三个主要元素:
- 一个客户端 ,它知道某种抽象策略的存在,但可能不知道该策略做什么或如何执行。
- 如果/其中有一个,客户可以使用的一组策略 。 这些可能以一流函数,对象,类或其他某种数据结构的形式出现。
- 客户可以提供给其当前策略以用于执行的可选上下文 。
实施策略的经典方法是使用接口。 在这种情况下,客户端具有指向某个抽象策略接口的内部指针,该接口随后通过依赖项注入(即在构造期间或在运行时使用setter)指向具体的策略实现。 此后,客户可以使用所提供的策略来执行一些工作,而所有这些都不知道(或关心)该策略的实际作用。
尽管接口是实现策略模式的经典方法,但是在没有接口的语言中也可以实现类似的效果。 重要的是,客户应了解某种抽象策略,并且能够在不了解其内部运作的情况下执行该策略。
通过纯继承实现多态
在研究如何使用策略模式之前,让我们看一些使用其他方法实现多态的示例。 考虑以下片段,该片段使用纯继承定义种族中不同类型的跑步者。
这里有一个Runner父类和从其继承的三个子类:Jogger; 短跑选手; 还有,马拉松运动员。 每个子类都使用其自己的实现覆盖父类的run方法。 随后,当我们实例化每种类型的跑步者并将它们传递给新的Race对象时,我们可以看到在比赛开始时每个跑步者都使用自己的跑步行为。
上面的代码片段有效,但是有一些问题。 首先,它有点blo肿,因为我们仅出于更改单个行为的目的而创建了许多子类。 如果跑步者以更多的方式变化,这可能是值得的; 但是,在此简单程序中,这些类可能是不必要的。 另一个也许更值得注意的问题是,我们的跑步者被永久设置为特定的子类。 例如,如果alice_ruby想要像马拉松运动员一样奔跑,那么就没有一种很好的方法来帮助她做到这一点,而又不完全改变自己的班级。
如果需要具有动态更改行为的能力,那么让我们看一个可能的解决方案。
幼稚的策略和控制流程
为了改进我们在运行程序中的早期实现,下面提供了一个不使用继承的重构版本。
在此版本中,我们只有一个Runner类,带有一个接受新参数:策略的构造函数。 在这种情况下,我们的策略只是一个符号,然后在重构的运行方法中使用它。 新的run方法包含一个case语句,该语句检查给定实例的strategy属性并相应地执行一些代码。 确实,这次我们开始比赛时,得到的输出与以前相同。
在某些方面,此程序的版本是对我们早期版本的改进,但在其他方面则是退后一步。 从好的方面来看,我们现在可以通过使用setter为其指定新的符号来更改给定跑步者的幼稚策略,如alice_ruby.strategy =:marathon一样。 通过这种方式,我们可以有效地更改特定对象的行为,而无需更改其类。 但是,Runner#run方法中的long case语句是有问题的。 这种控制流明显违反了OCP,因为我们不能在未打开进行修改的情况下扩展run方法。 那么,如果我们希望能够在仍然遵守OCP的前提下动态更改策略,该怎么办?
行动中的战略模式
在该程序的最终版本中,我们最终将使用策略模式。 在这种情况下,我们在自己的类中定义了一组策略,然后通过依赖注入将这些类提供给我们的跑步者。
就像在第二个片段中一样,我们的Runner类在构造时接受一个策略参数,并且还可以根据需要更改该策略。 但是,我们没有将简单的符号传递给Runner以在控件结构中使用,而是将其传递给RunStrategies模块中定义的几个策略类之一。 这些策略中的每一个都有一个run方法,这意味着我们的客户对象可以使用相同的代码执行其中的任何一个。 由于Ruby没有正式的接口,我们通过使每个策略都继承自RunStrategyInterface类来提供我们自己的简单错误检查机制,如果调用其run class方法,则会引发错误。 (如果策略无法自行实现此方法的版本,则RunStrategyInterface运行类方法将执行并引发错误,然后我们可以在部署之前进行测试。)
该程序运行时,会在实例化时为每个运行者提供所需的策略。 在程序执行期间,跑步者可以根据需要使用这些策略,并将自己的名称作为上下文传递给该策略。 而且,如果我们想在程序进行过程中更新特定跑步者的策略,则可以使用setter方法轻松完成,例如alice_ruby.strategy = RunStrategies :: Marathon。
通过使用策略模式,我们使程序能够在运行时根据上下文动态更改算法。 此外,我们的Runner#run方法是OCP一致的,因为我们可以通过简单地实施新策略(而不是在run方法中更改控件结构)来创建新行为。
TL; DR
策略模式是一种行为设计模式,用于在运行时动态选择和执行算法。 当给定类需要在不同时间以不同方式执行相同行为时,此模式特别有用。 该策略模式允许程序使用多态性而没有膨胀的类继承结构,并且仍保持与开放/闭合原理一致。 传统上,策略模式是使用接口抽象实现的,这使我们可以创建多个策略实现,以根据需要传递给客户端对象。 但是,通过遵循一组约定并实现自定义错误检查,可以在没有正式接口的语言中使用策略模式。
这就是我们对策略模式的讨论! 请继续关注将来关于其他设计模式的博客文章。
如果您希望在发表新文章时收到提醒,可以在Medium, Twitter上关注我,或者在交叉发布这些文章的我的个人博客中订阅。 编码愉快!
参考资料
![](https://i-blog.csdnimg.cn/blog_migrate/b0d6f63ec05bbde13549dff50a873b77.png)
翻译自: https://hackernoon.com/coding-dynamic-behavior-with-the-strategy-pattern-c0bebaee6671
mcs调制与编码策略