65.9K
CodeProject 正在变化。 阅读更多。
Home

使用策略设计模式创建部分通用操作(方法)的可重用组件 -保持简单系列

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (4投票s)

2019年5月6日

MIT

6分钟阅读

viewsIcon

15279

本文是“保持简单”系列的第一篇文章。本文旨在探讨如何使用策略设计模式创建部分通用操作(方法)的可重用组件。我们将探讨策略设计模式的必要性。

引言

开始之前,请允许我回答以下问题。

  • 问题是什么?
  • 为什么我们需要策略设计模式来解决这个问题?

让我通过一个例子来解释这个问题。有一个超类S,它有四个子类A、B、C和D。有一个操作(你可以说是一个方法)O,它有两个版本的实现。操作O的一个版本对子类A和C是通用的。操作O的另一个版本对子类B和D是通用的。我们不能简单地将这个操作O放在超类S中,因为操作O有两个版本的实现,并且这两个版本并非对所有子类都通用。操作O的每个版本都是部分通用的。

一个解决方案

我们可以为每个子类实现操作O所需的特定版本。但这样做我们会重复自己。一个好的设计不应该有重复。重复会使系统难以维护、难以扩展,并容易出现bug。所以,这个解决方案并不是一个完美的解决方案。

更简洁的解决方案

使用策略设计模式!

如果你仍然不理解这个问题,请不要担心。我们将在整篇文章中进行讨论。
现在让我们了解我们的超级英雄策略设计模式将如何拯救我们的一天。

背景

有一个简单的鸟类百科全书系统。它模拟鸟类的行为,如飞行、游泳等。其核心是一个鸟类引擎,它使所有这些成为可能。让我们讨论鸟类引擎的初始设计。有一个名为Bird的超类。它是一个abstract类,并包含所有通用操作(方法)的实现。有三个具体的鸟类,Swan(天鹅)、Albatross(信天翁)和Gull(海鸥),它们继承了Bird类。让我们看看图表,以便更好地理解。请注意,本文是设计文章,我们不会深入到代码层面的细节。保持简单。

Bird Engine v1.0.0.0

根据上图

Bird 类具有以下操作(方法)

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

Swan 类具有以下操作

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

AlbatrossGull 类也一样。

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

问题出现

变化是软件行业中唯一的常数。现在,在鸟类引擎中,我们需要添加另一个名为 Penguin 的鸟类。根据我们的设计,Penguin 类必须继承 Bird 类。因此,Penguin 类继承了 Bird 类的所有操作。但问题是 Bird 类有 fly() 操作,而我们可怜的企鹅不会飞。除非企鹅是《马达加斯加》里的Skipper。我们现在该怎么办?

我们可以将 fly() 操作从 Bird 类中提取出来,并将其实现到所有具体的子类中 - SwanAlbatrossGull。但是如果我们这样做,那么我们就会牺牲可重用性,并且我们会在所有具体的子类中重复自己,包括 Penguin 类。因为 Penguin 类应该实现 fly() 操作,但是不实现飞行功能。

那么,我们应该如何以更简洁的方式解决这个问题呢?

解决方案闪耀

为了更清晰地解决这个问题,我们从 Bird 类中提取了 fly() 操作的实现。然后我们创建了一个 FlyBehaviour 抽象类,并在其中定义了一个 fly() 抽象操作。

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

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

然后我们定义两个具体的子类 - CanFlyCannotFly,它们继承 FlyBehaviour 抽象类 类。

CanFly 类具有以下操作

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

CannotFly 类具有以下操作

  • fly():什么都不做,因为它适用于那些不会飞的鸟。

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

Bird Engine v1.0.0.1

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

Bird 抽象类结构仅更新

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

这就是所谓的组合。Bird 类型和 FlyBehaviour 类型的组合。识别应用程序中变化的部分,并将其与保持不变的部分分离。通过这种方式,我们可以创建部分通用操作的可重用组件。

又出现一个问题

现在我们需要在我们的鸟类引擎中再添加一种鸟类——Frigatebird(军舰鸟)。因此,根据设计,我们的 Frigatebird 类必须继承 Bird 类。所以,Frigatebird 类继承了 Bird 类的所有操作。但问题是,我们可怜的 Frigatebird 不会游泳。我们现在该怎么办?

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

解决方案再次闪耀

我不会再重复我自己了。所以,我让图表说话,你知道的

引用

一张图片胜过千言万语。

结论

我们用于创建部分通用操作的可重用组件的设计解决方案称为——策略设计模式。现在我们的鸟类引擎设计非常灵活,我们可以添加更多的具体鸟类类型而无需更改其他类。我们所需要做的就是添加类,仅此而已。每个引擎都应该以鼓励重用性、可扩展性和可维护性的方式进行设计。归根结底,一个好的设计将为你节省大量的精力和金钱。每个设计模式的目标都是创建一个可重用、可扩展和可维护的系统。

现在是策略设计模式的官方定义时间:定义一组算法,将每个算法封装起来,并使它们可以互换。策略模式让算法独立于使用它的客户端而变化。

参考文献

  • O'Reilly 出版的《Head First Design Patterns》

最后说明

感谢您的时间。如果您喜欢这篇文章,请给它评分、分享和评论。

祝您阅读愉快!

© . All rights reserved.