原文地址:http://www.codeproject.com/KB/cpp/applyingpatterns2.aspx
译者:赖勇浩(http://blog.youkuaiyun.com/lanphaday )
解决方案架构师:你的进度怎么样?
愚蠢的开发者:是的,我觉得我学会了如何应用 Observer 模式去解决所有问题
解决方案架构师:一个模式解决所有问题?
愚蠢的开发者:啊,还不够么?
介绍
关于本文
本文是这个系列的第二篇文章,在阅读本文之前,你应该已经读过并且理解这个系列的第一篇,题为
- 学习如何应用设计模式设计你的足球引擎(第一、二部分)
在第一篇文章中,我们讨论了
- 什么是模式,如何使用它们
- 如何支持方案应用模式
- 如何用观察者模式来解决我们的足球引擎的设计问题
本文是前一篇文章的延续,在这里我们将讨论
- 第三部分:应用 Strategy 模式解决“球队(Team)”和“球队策略(TeamStrategy)”相关的设计问题
- 第四部分:应用 Decorator 模式来解决“球员(Player)”相关的设计问题
如果你已经不记得这些设计问题,最好翻回第一篇文章去看看再回来继续。
第三部分
应用 Strategy 模式
在这一节我们将深入策略模式,然后我们应用这个模式来解决第二个设计问题。像前一篇文章那样,在这里我们再来回顾一下我们的第二个设计问题。
如何你能够记起,我们的第二个设计问题是:
- 特定的设计问题:在比赛进行时,终端用户能够改变它的球队的策略(如从进攻改为防守)
- 问题泛化:需要客户端(在这里就是球队)使用的算法(球队策略)能够独立改变
如前文所言,当比赛进行时,我们需要改变球队的策略(如从进攻改为防守)。确切来说就是需要从球队分享球队的策略。
我们已知可以利用策略模式来解决上述设计问题,因为它可使客户端(例如球队)使用的算法(例如球队策略)能够独立改变;那么我们来看看如何利用 Strategy 模式来解决这个设计问题。
理解 Strategy 模式
Strategy 模式非常简单,下面是 Strategy 模式的 UML 图:
Fig - Strategy Pattern
它包括如何几个部分
- 策略(Strategy)
这是一个算法(策略)的抽象类,所有的具体的算法都派生自它。简单地说,它为所有的具体算法(或具体的策略)提供了一个通用的接口。换言之,如果类 Strategy 有个抽象函数叫 foo(),那么所有具体的策略类都应该重载foo() 函数。
- 具体的策略(ConcreteStrategy)
这个类是我们真正实现算法的地方,或者说,它是类 Strategy 的具体实现。例如 Sort 是实现算法的策略类,而具体的策略可以是 MergeSort 或 QuickSort 等等。
- 上下文(Context)
Context可由一个或多个策略类配置,它通常策略接口访问具体策略对象。
应用 Strategy 模式
现在让我们想想如何用 Strategy 模式来解决问题。
Fig - Solving Our Second Design Problem
类 TeamStrategy 包含 Play 函数,AttackStrategy 和 DefendStrategy 是它的具体实现。Team 包含策略,策略可以根据比赛的形势而改变(例如,在领先了若干个进球后从进攻策略改为防守策略)。当用 Team 的PlayGame 函数时,它会调用当前策略的 Play 函数,这一切马上生效,干净利索。
通过策略模式,就可以从类 Team 中分离算法(例如团队的策略)。
Strategy 模式实现
TeamStrategy (Strategy)
下面是类 TeamStrategy 的代码:
- 'Strategy:TheTeamStrategyclass
- 'Thisclassprovidesanabstractinterface
- 'toimplementconcretestrategyalgorithms
- PublicMustInheritClassTeamStrategy
- 'AlgorithmInterface:Thisistheinterfaceprovided
- PublicMustOverrideSubPlay()
- EndClass'ENDCLASSDEFINITIONTeamStrategy
AttackStrategy (ConcreteStrategy)
下面是类 AttackStrategy 的代码,它派生自 TeamStrategy:
- 'ConcreteStrategy:TheAttackStrategyclass
- 'Thisclassisaconcreteimplementationofthe
- 'strategyclass.
- PublicClassAttackStrategy
- InheritsTeamStrategy
- 'OverridesthePlayfunction.
- 'Letusplaysomeattackinggame
- PublicOverridesSubPlay()
- 'Algorithmtoattack
- System.Console.WriteLine("Playinginattackingmode")
- EndSub
- EndClass'ENDCLASSDEFINITIONAttackStrategy
DefendStrategy (ConcreteStrategy)
下面是类 DefendStrategy 的代码,它也派生自 TeamStrategy:
- 'ConcreteStrategy:TheDefendStrategyclass
- 'Thisclassisaconcreteimplementationofthe
- 'strategyclass.
- PublicClassDefendStrategy
- InheritsTeamStrategy
- 'OverridesthePlayfunction.
- 'Letusgodefensive
- PublicOverridesSubPlay()
- 'Algorithmtodefend
- System.Console.WriteLine("Playingindefensivemode")
- EndSub
- EndClass'ENDCLASSDEFINITIONDefendStrategy
Team (Context)
下面是类 Team 的代码,根据我们的设计,一个球队在同一时间只能有一种策略:
- 'Context:TheTeamclass
- 'Thisclassencapsulatesthealgorithm
- PublicClassTeam
- 'Justavariabletokeepthenameofteam
- PrivateteamNameAsString
- 'Areferencetothestrategyalgorithmtouse
- PrivatestrategyAsTeamStrategy
- 'ContextInterfacetosetthestrategy
- PublicSubSetStrategy(ByValsAsTeamStrategy)
- 'Setthestrategy
- strategy=s
- EndSub
- 'Functiontoplay
- PublicSubPlayGame()
- 'Printtheteam'sname
- System.Console.WriteLine(teamName)
- 'Playaccordingtothestrategy
- strategy.Play()
- EndSub
- 'Constructortocreatethisclass,bypassingtheteam's
- 'name
- PublicSubNew(ByValteamNameAsString)
- 'Settheteamnametouselater
- Me.teamName=teamName
- EndSub
- EndClass'ENDCLASSDEFINITIONTeam
组合起来
创建球队,然后设置它们的策略,并开始比赛。下面的代码颇为简单而且有详细的注释。
- 'GameEngineclassfordemonstration
- PublicClassGameEngine
- PublicSharedSubMain()
- 'Letuscreateateamandsetitsstrategy,
- 'andmaketheteamsplaythegame
- 'Createfewstrategies
- DimattackAsNewAttackStrategy()
- DimdefendAsNewDefendStrategy()
- 'Createourteams
- DimfranceAsNewTeam("France")
- DimitalyAsNewTeam("Italy")
- System.Console.WriteLine("Settingthestrategies..")
- 'Nowletussetthestrategies
- france.SetStrategy(attack)
- italy.SetStrategy(defend)
- 'Maketheteamsstarttheplay
- france.PlayGame()
- italy.PlayGame()
- System.Console.WriteLine()
- System.Console.WriteLine("Changingthestrategies..")
- 'Letuschangethestrategies
- france.SetStrategy(defend)
- italy.SetStrategy(attack)
- 'Makethemplayagain
- france.PlayGame()
- italy.PlayGame()
- 'Waitforakeypress
- System.Console.Read()
- EndSub
- EndClass
运行
程序运行后的输出如下:
第四部分
应用 Decorator 模式
在这一节我们来讲讲如何应用 Decorator 模式来解决第三个设计问题(如有必要可参考前一篇文章)。这个问题是与球员的运行时职责指派相关的(如前锋、后卫等)。
你可以考虑创建一个球员类,然后基于它派生出类似前锋、中锋和后卫等多个子类。但它并非最好的解决方案,正如我们之前讨论的——一个球员可以在某个时刻是前锋,另一个时刻又变成了中锋。至少,在我们的足球引擎中是这样的,所以这是我们的设计问题。
特定的设计问题:球员拥有额外的职责,如前锋、后卫等,而且可以在运行时切换。
问题泛化:在不使用子类化的情况下,需要能够动态地为对象(在这里是指球员)挂接额外的职责(如前锋、中锋等)。
理解 Decorator 模式
Decorator 模式可以动态地为对象增加职责,是子类化之外的完美之选。下面是 Decorator 模式的 UML 图:
Fig - Decorator Pattern
这个模式由以下部分组成
- 组件(Component)
类 Component 为组件声明了一个抽象接口,我们能够在这些组件上挂接额外的职责。
- 具体的组件(ConcreteComponent)
类 ConcreteComponent 是类 Component 的具体实现,它真正定义了一个能够挂接的额外职责。
- 装饰者(Decorator)
类 Decorator 从类 Component 继承而来,这意味着它继承了组件的所有接口(函数、属性等),而且持有一个从组件类继承下来的对象的引用。其实一个具体的装饰者甚至能够持有其它装饰者的引用(因为类 Decorator 本来就是由类 Component 派生而来)。
- 具体的装饰者(Concrete Decorator)
这个类是真正为组件挂接职责的地方。
应用 Decorator 模式
现在看看如何应用 Decorator 模式来解决与球员相关的设计问题。
Fig - Solving Our Third Design Problem
可以看到从类 Player 继承两个具体的组件——GoalKeeper 和FieldPlayer,另外还有三个具体的装饰者——Forward、MidFielder和Defender。一个球队有 11 个全场球员和一个守门员(译注:作者写错了,应该是 10个全场球员,后文相同的错误不再指出)。我们的设计问题是需要在运行时指派类似前锋、后卫之类的职责给球员。虽然我们只有 11 全场球员,但可能同时有 11 个前锋和 11 个中锋,因为一个球员可以同时既是前锋又是中锋。通过给球员指派多个角色,和交换它们的角色等,使我们能够规划极佳的比赛策略。
例如可以在比赛的某一时刻通过暂时给一个球员指派 Forward 装饰者,使他可以冲刺和射门。
为具体的组件增加额外的职责,首先可以创建一个具体的组件,然后以引用的形式把它指派给装饰者。比如你可以创建一个全场球员,和一个中锋装饰者,将全场球员指派给中锋装饰者可以为它增加中锋的职责。最后,如果你想,你也可以把同一个球员指派给前锋装饰者。在示例代码中的 GameEngine 模块很好地解释了 Decorator 模式。
下面来看看它的实现,代码都加上了很多注释。
Decorator 模式实现
Player (Component)
类 Player 的实现如下:
- 'Component:ThePlayerclass
- PublicMustInheritClassPlayer
- 'Justgiveanameforthisplayer
- PrivatemyNameAsString
- 'Thepropertytoget/setthename
- PublicPropertyName()AsString
- Get
- ReturnmyName
- EndGet
- Set(ByValValueAsString)
- myName=Value
- EndSet
- EndProperty
- 'ThisistheOperationinthecomponent
- 'andthiswillbeoverridedbyconcretecomponents
- PublicMustOverrideSubPassBall()
- EndClass'ENDCLASSDEFINITIONPlayer
FieldPlayer (ConcreteComponent)
下面是类 FieldPlayer 的实现:
- 'ConcreteComponent:FieldPlayerclass
- 'Thisisaconcretecomponent.Later,wewilladdadditionalresponsibilities
- 'likeForward,Defenderetctoafieldplayer.
- PublicClassFieldPlayer
- InheritsPlayer
- 'Operation:OverridesPassBalloperation
- PublicOverridesSubPassBall()
- System.Console.WriteLine("Fieldplayer({0})-passedtheball",_
- MyBase.Name)
- EndSub
- 'Aconstructortoacceptthenameoftheplayer
- PublicSubNew(ByValplayerNameAsString)
- MyBase.Name=playerName
- EndSub
- EndClass'ENDCLASSDEFINITIONFieldPlayer
GoalKeeper (ConcreteComponent)
下面是类 GoalKeeper 的实现:
- 'ConcreteComponent:GaolKeeperclass
- 'Thisisaconcretecomponent.Later,wecanaddadditionalresponsibilities
- 'tothisclassifrequired.
- PublicClassGoalKeeper
- InheritsPlayer
- 'Operation:Overridingthebaseclassoperation
- PublicOverridesSubPassBall()
- System.Console.WriteLine("GoalKeeper({0})-passedtheball",MyBase.Name)
- EndSub
- 'Aconstructortoacceptthenameoftheplayer
- PublicSubNew(ByValplayerNameAsString)
- MyBase.Name=playerName
- EndSub
- EndClass'ENDCLASSDEFINITIONGoalKeeper
PlayerRole (Decorator)
下面是类 PlayerRole 的实现:
- 'Decorator:PlayerRoleisthedecorator
- PublicClassPlayerRole
- Inheritsplayer
- 'Thereferencetotheplayer
- ProtectedplayerAsplayer
- 'Callthebasecomponent'sfunction
- PublicOverridesSubPassBall()
- player.PassBall()
- EndSub
- 'Thisfunctionisusedtoassignaplayertothisrole
- PublicSubAssignPlayer(ByValpAsplayer)
- 'Keepareferencetotheplayer,towhomthis
- 'roleisgiven
- player=p
- EndSub
- EndClass'ENDCLASSDEFINITIONPlayerRole
Forward (ConcreteDecorator)
下面是类 Forward 的实现:
- 'ConcreteDecorator:ForwardclassisaConcreteimplementation
- 'ofthePlayerRole(Decorator)class
- PublicClassForward
- InheritsPlayerRole
- 'AddedBehavior:ThisisaresponsibilityexclusivelyfortheForward
- PublicSubShootGoal()
- System.Console.WriteLine("Forward({0})-Shootedtheballtogoalpost",_
- MyBase.player.Name)
- EndSub
- EndClass'ENDCLASSDEFINITIONForward
MidFielder (ConcreteDecorator)
下面是类 MidFielder 的实现:
- 'ConcreteDecorator:MidFielderclassisaConcreteimplementation
- 'ofthePlayerRole(Decorator)class
- PublicClassMidFielder
- InheritsPlayerRole
- 'AddedBehavior:ThisisaresponsibilityexclusivelyfortheMidfielder
- '(Don'taskmewhetheronlymidfilderscandribbletheball-atleast
- 'itissoinourengine)
- PublicSubDribble()
- System.Console.WriteLine("Midfielder({0})-dribbledtheball",_
- MyBase.player.Name)
- EndSub
- EndClass'ENDCLASSDEFINITIONMidfielder
Defender (ConcreteDecorator)
下面是类 Defender 的实现:
- 'ConcreteDecorator:DefenderclassisaConcreteimplementation
- 'ofthePlayerRole(Decorator)class
- PublicClassDefender
- InheritsPlayerRole
- 'AddedBehavior:ThisisaresponsibilityexclusivelyfortheDefender
- PublicSubDefend()
- System.Console.WriteLine("Defender({0})-defendedtheball",_
- MyBase.player.Name)
- EndSub
- EndClass'ENDCLASSDEFINITIONDefender
组合起来
- 'Letusputittogether
- PublicClassGameEngine
- PublicSharedSubMain()
- '--Step1:
- 'Createfewplayers(concretecomponents)
- 'CreatefewfieldPlayers
- DimowenAsNewFieldPlayer("Owen")
- DimbeckAsNewFieldPlayer("Beckham")
- 'Createagoalkeeper
- DimkhanAsNewGoalKeeper("Khan")
- '--Step2:
- 'Justmakethempasstheball
- '(duringawarmupsession;))
- System.Console.WriteLine()
- System.Console.WriteLine(">WarmupSession...")
- owen.PassBall()
- beck.PassBall()
- khan.PassBall()
- '--Step3:Createandassigntheresponsibilities
- '(whenthematchstarts)
- System.Console.WriteLine()
- System.Console.WriteLine(">Matchisstarting..")
- 'Setowenasourfirstforward
- Dimforward1AsNewForward()
- forward1.AssignPlayer(owen)
- 'SetBeckhamasourmidfielder
- Dimmidfielder1AsNewMidFielder()
- midfielder1.AssignPlayer(beck)
- 'Now,usetheseplayerstodoactions
- 'specifictotheirroles
- 'Owencanpasstheball
- forward1.PassBall()
- 'Andowencanshootaswell
- forward1.ShootGoal()
- 'Beckhamcanpassball
- midfielder1.PassBall()
- 'Beckhamcandribbletoo
- midfielder1.Dribble()
- '[Arrangetheaboveoperationstosomemeaningfullsequence,like
- '"Beckhamdribbledandpassedtheballtoowenandowenshootedthe
- 'goal;)-justforsomefun]"
- '--Step4:Now,changingresponsibilities
- '(duringasubstitution)
- 'Assumethatowengotinjured,andweneedanewplayer
- 'toplayasourforward1
- System.Console.WriteLine()
- System.Console.WriteLine(">OOps,Owengotinjured."&_
- "JerrardreplacedOwen..")
- 'Createanewplayer
- DimjerrardAsNewFieldPlayer("Jerrard")
- 'AskJerrardtoplayinpositionofowen
- forward1.AssignPlayer(jerrard)
- forward1.ShootGoal()
- '--Step5:Addingmultipleresponsibilities
- '(Whenaplayerneedtohandlemultipleroles)
- 'WealreadyhaveBeckhamasourmidfielder.
- 'Letusaskhimtoplayasanadditionalforward
- DimonemoreForwardAsNewForward()
- onemoreForward.AssignPlayer(beck)
- System.Console.WriteLine()
- System.Console.WriteLine(">Beckhamhasmultipleresponsibilities..")
- 'NowBeckhamcanshoot
- onemoreForward.ShootGoal()
- 'Andusehisearlierresponsibilitytodribbletoo
- midfielder1.Dribble()
- 'Accordingtoourdesign,youcanattachtheresponsibilityof
- 'aforwardtoagoalkeepertoo,butwhenyouactually
- 'playfootball,rememberthatitisdangerous;)
- 'Waitforkeypress
- System.Console.Read()
- EndSub
- EndClass
运行
下面是运行程序的输出
结论
在本文中我们讨论了
- 模式及其实现
- 模式及其实现
暂时就这么多了。事实上,正是 code project 社区对我前一篇文章的支持才鼓气我的勇气来发表这一篇。谢谢所有人的支持和鼓励!