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

设计你的足球引擎,并学习如何应用设计模式(观察者、装饰器、策略和建造者模式)-第三部分和第四部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (65投票s)

2005 年 11 月 9 日

CPOL

9分钟阅读

viewsIcon

128609

downloadIcon

621

本文是对上一篇文章的延续,在本文中,我们将讨论(1)应用策略模式来解决与“球队”和“球队策略”相关的设计问题,以及(2)应用装饰器模式来解决与“球员”相关的设计问题。

目录


解决方案架构师:“有什么进展吗?”

愚蠢的开发者:“是的,我想我学会了如何应用观察者模式来解决所有问题”

解决方案架构师:“一种模式解决所有问题?”

愚蠢的开发者:“呃,这还不够吗?”

引言

本文简介

这是本系列文章的第二篇。在阅读本文之前,您应该阅读并理解本系列的第一篇文章,标题为:设计你的足球引擎,并学习如何应用设计模式(观察者、装饰器、策略和建造者模式)-第一部分和第二部分

在我第一篇文章(参见http://amazedsaint.blogspot.com,包含第一和第二部分)中,我们讨论了

  • 什么是模式,以及如何使用它们
  • 如何识别应用模式的场景
  • 如何应用观察者模式来解决我们足球引擎中的设计问题

本文是对上一篇文章的延续,在本文中,我们将讨论

  • 第三部分:应用策略模式来解决与“球队”和“球队策略”相关的设计问题
  • 第四部分:应用装饰器模式来解决与“球员”相关的设计问题

如果您不记得这些设计问题,请返回第一篇文章,参考它们,然后回来。

使用代码

  • 相关的 zip 文件包括用于演示策略模式和装饰器模式应用的源代码、UML 设计(Visio 格式)等。阅读本文后,您可以下载并解压缩 zip 文件 - 使用 WinZip 等程序 - 来玩源代码。

第三部分

应用策略模式

在本节中,我们将仔细研究策略模式,然后将其应用于解决我们的第二个设计问题。此时请参考上一篇文章 - 提醒您我们的第二个设计问题。

如果您还记得,我们的第二个设计问题是

  • 具体设计问题:比赛进行时,最终用户可以更改其球队的策略(例如,从进攻到防守)。
  • 问题泛化:我们需要让算法(TeamStrategy)独立于使用它的客户端(在本例中是Team)而变化。

正如我们之前讨论过的,当比赛进行时,我们需要更改球队的策略(例如,从进攻到防守)。这清楚地意味着我们需要将球队的策略与其使用的球队分离开来。

我们知道,我们可以应用策略模式来解决上述设计问题,因为它允许算法(即球队的策略)独立于使用它的客户端(即球队)而变化。让我们看看如何应用策略模式来解决这个设计问题。

理解策略模式

策略模式非常简单。策略模式的 UML 图如下所示

图 - 策略模式

模式的参与者详细如下

  • 策略
  • 此类是算法(或策略)的抽象类,所有具体算法都从中派生。简而言之,它提供了一个所有具体算法(或具体策略)的通用接口。也就是说,如果 `Strategy` 类中有一个抽象(必须覆盖)函数 `foo()`,那么所有具体策略类都应该覆盖 `foo()` 函数。

  • ConcreteStrategy
  • 此类是实际实现算法的地方。换句话说,它是 `Strategy` 类的具体实现。举个例子,如果 `Sort` 是实现算法的策略类,那么具体策略可以是 `MergeSort`、`QuickSort` 等。

  • 背景
  • `Context` 可以配置一个或多个具体策略。它将通过策略接口访问具体策略对象。

适配策略模式

现在,让我们看看我们实际上是如何适配策略模式来解决我们的问题的。这将给你一个非常清晰的图景。

图 - 解决我们的第二个设计问题

在这里,`TeamStrategy` 类包含 `Play` 函数。`AttackStrategy` 和 `DefendStrategy` 是 `TeamStrategy` 类的具体实现。`Team` 持有一个策略,并且可以根据比赛情况更改此策略(例如,如果我们领先几个球,我们将活动策略从 `AttackStrategy` 更改为 `DefendStrategy` - 呃,好吧,我不是一个好的足球教练)。当我们调用 `Team` 中的 `PlayGame` 函数时,它会调用当前策略的 `Play` 函数。看看代码。它很简单,并且注释得很整齐。

通过使用策略模式,我们将算法(即球队的策略)与 `Team` 类分开了。

策略模式实现

TeamStrategy (Strategy)

下面显示了 `TeamStrategy` 类的代码

'Strategy: The TeamStrategy class

'This class provides an abstract interface 
'to implement concrete strategy algorithms

Public MustInherit Class TeamStrategy

'AlgorithmInterface : This is the interface provided
Public MustOverride Sub Play ()

End Class ' END CLASS DEFINITION TeamStrategy

AttackStrategy (ConcreteStrategy)

下面显示了 `AttackStrategy` 类的代码。它派生自 `TeamStrategy`。

'ConcreteStrategy: The AttackStrategy class

'This class is a concrete implementation of the
'strategy class.

Public Class AttackStrategy
Inherits TeamStrategy

    'Overrides the Play function. 
    'Let us play some attacking game

    Public Overrides Sub Play()
        'Algorithm to attack
        System.Console.WriteLine(" Playing in attacking mode")
    End Sub

End Class ' END CLASS DEFINITION AttackStrategy

DefendStrategy (ConcreteStrategy)

下面显示了 `DefendStrategy` 类的代码。它派生自 `TeamStrategy`。

'ConcreteStrategy: The DefendStrategy class

'This class is a concrete implementation of the
'strategy class.

Public Class DefendStrategy
Inherits TeamStrategy

    'Overrides the Play function. 
    'Let us go defensive
    Public Overrides Sub Play()
        'Algorithm to defend
        System.Console.WriteLine(" Playing in defensive mode")
    End Sub

End Class ' END CLASS DEFINITION DefendStrategy

Team (Context)

下面显示了 `Team` 类的代码。根据我们的设计,一支球队一次只能有一个策略。

'Context: The Team class
'This class encapsulates the algorithm

Public Class Team

    'Just a variable to keep the name of team
    Private teamName As String


    'A reference to the strategy algorithm to use
    Private strategy As TeamStrategy

    'ContextInterface to set the strategy
    Public Sub SetStrategy(ByVal s As TeamStrategy)
        'Set the strategy
        strategy = s
    End Sub

    'Function to play
    Public Sub PlayGame()
        'Print the team's name
        System.Console.WriteLine(teamName)
        'Play according to the strategy
        strategy.Play()
    End Sub

    'Constructor to create this class, by passing the team's name
    Public Sub New(ByVal teamName As String)
        'Set the team name to use later
        Me.teamName = teamName
    End Sub

End Class ' END CLASS DEFINITION Team

整合

这是 `GameEngine` 类,用于创建球队、设置它们的策略并让它们比赛。代码非常简单,并且注释很详细。

'GameEngine class for demonstration
Public Class GameEngine

    Public Shared Sub Main()

        'Let us create a team and set its strategy,
        'and make the teams play the game

        'Create few strategies
        Dim attack As New AttackStrategy()
        Dim defend As New DefendStrategy()

        'Create our teams
        Dim france As New Team("France")
        Dim italy As New Team("Italy")

        System.Console.WriteLine("Setting the strategies..")

        'Now let us set the strategies
        france.SetStrategy(attack)
        italy.SetStrategy(defend)

        'Make the teams start the play
        france.PlayGame()
        italy.PlayGame()

        System.Console.WriteLine()
        System.Console.WriteLine("Changing the strategies..")

        'Let us change the strategies
        france.SetStrategy(defend)
        italy.SetStrategy(attack)

        'Make them play again
        france.PlayGame()
        italy.PlayGame()

        'Wait for a key press
        System.Console.Read()

    End Sub

End Class

运行项目

执行项目,您将获得以下输出


第四部分

应用装饰器模式

在本节中,我们将研究如何应用装饰器模式来解决我们的第三个设计问题(如果需要,请参考上一篇文章)。我们的第三个设计问题与在运行时为球员分配职责(如前锋、中场等)有关。

您可以考虑创建一个球员类,然后派生子类,如前锋、中场、后卫等。但这并非最佳解决方案,因为正如我们之前讨论过的,一个球员可能是一个前锋,而在另一时间,同一个球员可能是中场。至少在我们的足球引擎中会是这样(有足球专家吗?;))。所以,这些是我们的设计问题

具体设计问题:球队中的一名球员应该具有额外的职责,如前锋、后卫等,这些职责可以在运行时分配。

问题泛化:我们需要在不使用子类化的情况下,动态地将额外的职责(如前锋、中场等)附加到对象(在本例中是 `Player`)上。

理解装饰器模式

装饰器模式可用于动态地为对象添加职责。它还提供了一种极好的子类化替代方案。装饰器模式的 UML 图如下所示

图 - 装饰器模式

模式的参与者详细如下

  • 组件 (Component)
  • `Component` 类表示组件的抽象接口。稍后,我们将这些组件附加额外的职责。

  • ConcreteComponent
  • `ConcreteComponent` 类是 `Component` 类的具体实现。它实际上定义了一个可以附加额外职责的对象。

  • 装饰器 (Decorator)
  • `Decorator` 类派生自 `Component` 类。这意味着它继承了 `Component` 的所有接口(函数、属性等)。它还保留了继承自 `Component` 类的对象的引用。因此,一个具体装饰器也可以保留对其他具体装饰器的引用(因为 `Decorator` 类继承自 `Component` 类)。

  • ConcreteDecorator
  • 此类是我们实际将职责附加到组件的地方。

适配装饰器模式

现在,是时候适配装饰器模式来解决我们与球员相关设计问题了。

图 - 解决我们的第三个设计问题

您可以看到我们有两个具体组件 `GoalKeeper` 和 `FieldPlayer`,它们都继承自 `Player` 类。我们有三个具体装饰器:`Forward`、`MidFielder` 和 `Defender`。对于一支球队,我们可能需要 11 名场上球员和 1 名守门员。我们的设计意图是,我们需要在运行时将前锋、后卫等职责分配给球员。我们只有 11 名场上球员 - 但我们有可能同时拥有 11 名前锋和 11 名中场,因为一名球员可以同时成为前锋和中场。这将使我们能够通过为球员分配多个角色、交换他们的角色等来制定良好的比赛策略。

例如,您可以要求一名球员在比赛的某个时刻上前射门,方法是暂时将他分配给 `Forward` 装饰器。

要为具体组件添加额外职责,您首先创建一个具体组件的对象,然后将其作为装饰器的引用进行分配。例如,您可以创建一个场上球员和一个中场装饰器,然后您可以将场上球员分配给中场装饰器,以将中场球员的职责添加到您的球员中。稍后,如果您愿意,可以将同一名球员分配给前锋装饰器对象。装饰器模式示例代码的 **GameEngine** 模块很好地解释了这一点。

请参见下面的实现。它经过大量注释。

装饰器模式实现

Player (Component)

下面显示了 `Player` 类的实现

' Component: The Player class

Public MustInherit Class Player

    'Just give a name for this player
    Private myName As String

    'The property to get/set the name
    Public Property Name() As String
        Get
            Return myName
        End Get
        Set(ByVal Value As String)
            myName = Value
        End Set
    End Property

    'This is the Operation in the component
    'and this will be overrided by concrete components
    Public MustOverride Sub PassBall()

End Class ' END CLASS DEFINITION Player

FieldPlayer (ConcreteComponent)

下面显示了 `FieldPlayer` 类的实现

' ConcreteComponent : Field Player class

'This is a concrete component. Later, we will add additional responsibilities
'like Forward, Defender etc to a field player.

Public Class FieldPlayer
Inherits Player

    'Operation: Overrides PassBall operation
    Public Overrides Sub PassBall ()
        System.Console.WriteLine(_
          " Fieldplayer ({0}) - passed the ball", _
          MyBase.Name)
    End Sub

    'A constructor to accept the name of the player
    Public Sub New(ByVal playerName As String)
        MyBase.Name = playerName
    End Sub

End Class ' END CLASS DEFINITION FieldPlayer

GoalKeeper (ConcreteComponent)

下面显示了 `GoalKeeper` 类的实现

' ConcreteComponent : GaolKeeper class

'This is a concrete component.
'Later, we can add additional responsibilities
'to this class if required.

Public Class GoalKeeper
Inherits Player

    'Operation: Overriding the base class operation
    Public Overrides Sub PassBall ()
    System.Console.WriteLine(" GoalKeeper " & _ 
      "({0}) - passed the ball", MyBase.Name)
    End Sub

    'A constructor to accept the name of the player
    Public Sub New(ByVal playerName As String)
        MyBase.Name = playerName
    End Sub

End Class ' END CLASS DEFINITION GoalKeeper

PlayerRole (Decorator)

下面显示了 `PlayerRole` 类的实现

'Decorator: PlayerRole is the decorator

Public Class PlayerRole
Inherits player

    'The reference to the player
    Protected player As player

    'Call the base component's function
    Public Overrides Sub PassBall()
        player.PassBall()
    End Sub

    'This function is used to assign a player to this role
    Public Sub AssignPlayer(ByVal p As player)
        'Keep a reference to the player, to whom this
        'role is given
        player = p
    End Sub

End Class ' END CLASS DEFINITION PlayerRole

Forward (ConcreteDecorator)

下面显示了 `Forward` 类的实现

'ConcreteDecorator: Forward class is a Concrete implementation
'of the PlayerRole (Decorator) class

Public Class Forward
Inherits PlayerRole

    'Added Behavior: This is a responsibility exclusively for the Forward
    Public Sub ShootGoal()
        System.Console.WriteLine(" Forward ({0}) - " & _ 
          "Shooted the ball to goalpost", _
          MyBase.player.Name)
    End Sub

End Class ' END CLASS DEFINITION Forward

MidFielder (ConcreteDecorator)

下面显示了 `MidFielder` 类的实现

'ConcreteDecorator: MidFielder class is a Concrete implementation
'of the PlayerRole (Decorator) class

Public Class MidFielder
Inherits PlayerRole

    'AddedBehavior: This is a responsibility exclusively for the Midfielder
    '(Don't ask me whether only mid filders can dribble the ball - atleast
    'it is so in our engine)

    Public Sub Dribble()
        System.Console.WriteLine(_
          " Midfielder ({0}) - dribbled the ball", _
          MyBase.player.Name)
    End Sub

End Class ' END CLASS DEFINITION Midfielder

Defender (ConcreteDecorator)

下面显示了 `Defender` 类的实现

'ConcreteDecorator: Defender class is a Concrete implementation
'of the PlayerRole (Decorator) class

Public Class Defender
Inherits PlayerRole

    'Added Behavior: This is a responsibility exclusively for the Defender
    Public Sub Defend()
        System.Console.WriteLine(_
          " Defender ({0}) - defended the ball", _
          MyBase.player.Name)
    End Sub

End Class ' END CLASS DEFINITION Defender

整合

'Let us put it together
Public Class GameEngine

Public Shared Sub Main()

    '-- Step 1: 
    'Create few players (concrete components)

    'Create few field Players
    Dim owen As New FieldPlayer("Owen")
    Dim beck As New FieldPlayer("Beckham")

    'Create a goal keeper
    Dim khan As New GoalKeeper("Khan")

    '-- Step 2: 
    'Just make them pass the ball 
    '(during a warm up session ;))

    System.Console.WriteLine()
    System.Console.WriteLine(" > Warm up Session... ")

    owen.PassBall()
    beck.PassBall()
    khan.PassBall()

    '-- Step 3: Create and assign the responsibilities
    '(when the match starts)

    System.Console.WriteLine()
    System.Console.WriteLine(" > Match is starting.. ")

    'Set owen as our first forward
    Dim forward1 As New Forward()
    forward1.AssignPlayer(owen)

    'Set Beckham as our midfielder
    Dim midfielder1 As New MidFielder()
    midfielder1.AssignPlayer(beck)

    'Now, use these players to do actions
    'specific to their roles

    'Owen can pass the ball
    forward1.PassBall()
    'And owen can shoot as well
    forward1.ShootGoal()

    'Beckham can pass ball
    midfielder1.PassBall()
    'Beckham can dribble too
    midfielder1.Dribble()

    ' [ Arrange the above operations to some meaningfull sequence, like
    ' "Beckham dribbled and passed the ball to owen and owen shooted the
    ' goal ;) - just for some fun ]"

    '-- Step 4: Now, changing responsibilities
    '(during a substitution)

    'Assume that owen got injured, and we need a new player
    'to play as our forward1

    System.Console.WriteLine()
    System.Console.WriteLine(" > OOps, Owen " _ 
       "got injured. " & _
       "Jerrard replaced Owen.. ")

    'Create a new player
    Dim jerrard As New FieldPlayer("Jerrard")

    'Ask Jerrard to play in position of owen
    forward1.AssignPlayer(jerrard)
    forward1.ShootGoal()

    '-- Step 5: Adding multiple responsibilities
    '(When a player need to handle multiple roles)

    'We already have Beckham as our midfielder. 
    'Let us ask him to play as an additional forward

    Dim onemoreForward As New Forward()
    onemoreForward.AssignPlayer(beck)

    System.Console.WriteLine()
    System.Console.WriteLine(" > Beckham has " & _ 
           "multiple responsibilities.. ")

    'Now Beckham can shoot
    onemoreForward.ShootGoal()
    'And use his earlier responsibility to dribble too
    midfielder1.Dribble()

    'According to our design, you can attach the responsibility of
    'a forward to a goal keeper too, but when you actually 
    'play football, remember that it is dangerous ;)

    'Wait for key press
    System.Console.Read()

End Sub

End Class

运行项目

执行项目后,您将获得以下输出

结论

在本文中,我们讨论了

  • 策略模式及其实现
  • 装饰器模式及其实现

就是这些了。事实上,CodeProject 社区对我第一篇文章的压倒性反响激励我发表了这篇文章。感谢大家的支持和鼓励。


附录 - 我博客中有几篇有趣的读物

历史

  • “有一天你也会成为历史的一部分。其他人只会记得你所给予的。”
  • 2005 年 11 月 09 日 - 准备好发表本文。
© . All rights reserved.