策略模式:设计模式中的灵活性典范

在软件开发中,经常会遇到需要根据不同情况执行不同操作的问题。为了解决这类问题,可以使用策略模式(Strategy Design Pattern)。策略模式是一种行为设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互换使用。这种模式让算法的变化独立于使用算法的客户。

让通过一个具体的例子来解释这个问题。假设有一个超类S,它有四个子类A、B、C和D。存在一个操作O,它有两个版本的实现。一个版本的操作O对子类A和C是通用的,另一个版本的操作O对子类B和D是通用的。不能简单地将操作O放入超类S中,因为操作O有两个版本的实现,这两个版本并不适用于所有子类。每个版本的操作O只是部分通用。

一种解决方案是为每个子类实现所需的操作O版本。但这样做会导致代码重复。一个好的设计不应该有重复。重复会使系统难以维护、难以扩展,并且容易出现错误。因此,这种解决方案并不完美。

更干净的解决方案是使用策略模式

有一个简单的鸟类百科全书系统,它模拟了鸟类的行为,如飞行、游泳等。在系统内部,有一个鸟类引擎,使得所有这些行为成为可能。让讨论一下鸟类引擎的初始设计。有一个名为Bird的超类,它是一个抽象类,包含了所有通用操作(方法)的实现。有三个具体的鸟类类——Swan、Albatross和Gull,它们继承了Bird类。

根据上面的图表:

  • Bird类有以下操作(方法):
    • fly():飞行逻辑的实现
    • swim():游泳逻辑的实现
    • walk():行走逻辑的实现
    • makeSomeNoise():这是一个抽象操作(一个没有实现的骨架),因为每只鸟都有独特的声音。所以每个子类都必须提供自己的实现。
    • display():这是一个抽象操作。每只鸟都有独特的外观。所以每个子类都必须提供自己的实现。

Swan类有以下操作:

  • makeSomeNoise():天鹅声音的实现
  • display():天鹅外观的实现

Albatross和Gull类也有类似的操作。

所有通用的鸟类操作都在Bird超类中定义,这样就不需要在具体的鸟类类中重复自己。一个好的设计总是鼓励可重用性。

问题出现

在软件行业中,变化是唯一不变的。现在在鸟类引擎中,需要添加另一个鸟类类——Penguin。Penguin类必须继承Bird类,根据设计。所以,Penguin类继承了Bird类的所有操作。但问题是Bird类有fly()操作,而企鹅不能飞。除非企鹅是马达加斯加的Skipper。现在应该怎么办?

可以从Bird类中提取出fly()操作,并在所有具体的子类——Swan、Albatross和Gull中实现它。但这样做,就会牺牲可重用性,并且在包括Penguin类在内的所有具体子类中重复自己。因为Penguin类应该实现fly()操作,但没有飞行实现。

那么,应该如何解决这个问题呢?

解决方案

为了以更干净的方式解决这个问题,从Bird类中提取出fly()操作的实现。然后创建一个FlyBehaviour抽象类,并在其中定义一个fly()抽象操作。

FlyBehaviour抽象类有以下抽象操作:

  • fly():这是一个抽象操作,因为有两个版本的fly()操作。

然后定义两个具体的子类——CanFly和CannotFly,它们继承FlyBehaviour抽象类。

CanFly类有以下操作:

  • fly():鸟类飞行的实现

CannotFly类有以下操作:

  • fly():什么都不做,因为它是为那些不能飞的鸟类准备的。

让看看更新后的图表,而不是让事情变得更复杂。

在这里,FlyBehaviour是一个接口,因为它没有实现。

Bird抽象类结构更新如下:

  • flyBehaviour:这是一个FlyBehaviour类型的属性。它可以持有CanFly或CannotFly具体类型。它可以通过setFlyBehaviour()操作设置。
  • setFlyBehaviour():它将FlyBehaviour的具体子类类型设置为flyBehaviour属性。如果具体的Bird类型是Penguin,那么flyBehaviour应该设置为CannotFly类型。对于其他具体的Bird类型,flyBehaviour应该设置为CanFly类型。
  • fly():现在,这个操作只是调用flyBehaviour属性上的fly()操作。底层的具体类型(CanFly或CannotFly)处理操作行为。

这被称为组合。Bird类型和FlyBehaviour类型的组合。识别应用程序中变化的方面,并将它们与保持不变的方面分开。这样,就可以创建一个可重用的组件,部分通用操作。

另一个问题出现

现在需要在鸟类引擎中添加另一种鸟类——Frigatebird。所以,Frigatebird类必须继承Bird类,根据设计。所以,Frigatebird类继承了Bird类的所有操作。但问题是Frigatebird不能游泳。现在应该怎么办?

最好知道现在应该做什么。试着自己解决这个问题。可以在下面找到它的解决方案,但不要作弊。

再次解决方案

不会再次重复自己。所以让图表说话,因为知道:

一幅图胜过千言万语。

用来创建部分通用操作的可重用组件的设计解决方案被称为策略模式。现在鸟类引擎设计非常灵活,可以添加更多的具体鸟类类型,而不需要改变其他类。所需要做的就是添加类,就是这样。每个引擎都应该设计成鼓励可重用性、可扩展性可维护性。最终,一个好的设计会为节省大量的努力和金钱。每个设计模式的目标都是创建一个可重用、可扩展和可维护的系统。

现在是官方定义策略模式的时候了:

定义一系列算法,将每一个算法封装起来,并使它们可以互换。策略模式允许算法的变化独立于使用算法的客户。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485