原文地址:
http://www.codeproject.com/KB/architecture/applyingpatterns.aspx
译者:赖勇浩(http://blog.csdn.net/lanphaday )
译者说:这是一篇非常好的文章,有非常棒的例子,非常棒的文笔,非常棒的代码(VB.net编写的,但你肯定读得懂),如果你还不懂设计模式,那它肯定是最适合你的 DPs 文章之一。
第一部分
解决方案架构师:你可以尝试使用模式
愚蠢的开发者:好的,它像 ActiveX 控件那样用吗?"
介绍
关于本文
本文希望能够做到
- 以简单、可读的方式向你介绍模式
- 教你如何真正“应用”模式(模式易学,但必须有过硬的设计本领才能应用它们解决问题)
- 让你认清应用 Builder、Observer、Strategy和 Decorator(这几个可是少数极常用的模式)模式的时机。
- 展示如何用 Observer 模式解决设计难题
全文通过如下内容依次推进
- 为一个简单足球游戏引擎建立模型
- 确定足球游戏引擎中的设计问题
- 决定用哪些模式来解决设计问题
- 然后真正地利用 observer 模式来解决其中一个设计问题。
先决条件
- 你需要懂得一些阅读和理解 UML 图的知识。
代码使用指南
- 相应的 zip 文件包含了代码、UML设计图(visio 格式)等,你可以使用 Winzip 等压缩软件解压。
简说设计模式
即使对设计模式知之甚少,设计师和开发者也会倾向于重用类和对象间来简化设计过程。简言之就是“设计模式考虑了多种对象(类、关系等)间的协作”,为常见的设计问题提供解决方案。最为重要的是他们为设计师和程序员提供一些“行话”来谈论他们的设计。例如你可以告诉你的朋友你使用了 Builder 模式来解决你项目中的一些问题。
Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides[即知名的四人帮(GOF)]为常见的设计问题提供了一致的分类模式。GOF 模式被认为是其它所有模式的基础。
使用模式的基本原则是可重用性。如果你正确理解了以模式为中心的软件工程概念,当遇到问题时你就不会重复发明轮子。这里有一些关于设计模式的重要观点:
- 设计模式不是代码,实际上它是一种解决问题的方法或模型。
- 设计模式是关于设计和对象间互动的,为它们提供解决常见设计问题的可重用的解决方案。
- 设计模式通常可以用 UML 图来表示。
真正的动手的经验可以给你更好的理念。
架构(简单)足球引擎
假设你在一家游戏开发公司供职,上头决定让你为公司的重要项目——足球游戏引擎做一套解决方案架构(很棒,哈哈)。现在由你领导设计整个足球游戏引擎,突然你就多了许多要考虑的事情,比如:
- 在游戏系统中如何标识实体,
- 如何确定设计问题所在,
- 如何应用模式来搞定你的设计说明书?
标识实体
首先,需要标识游戏引擎中的所有对象。因此你要想像一下终端用户将如何使用这个系统,现在假设终端用户将用以下序列来操作游戏(先简单化):
- 打开游戏
- 选择两支球队
- 配置球员
- 选择球场
- 开始
系统是可能有若干个球场(PlayGrounds)和球队(Teams)。系统中实际上起码有这些对象:
- 球员(Player),踢球的人。
- 球队(Team),包含若干球员。
- 球(Ball),球员所持有的物体。
- 球场(PlayGround),比赛进行的地方。
- 裁判(Referee),球场上控制比赛的人。
另外,游戏引擎中还有一些逻辑对象,如:
- 游戏(Game),定义了足球比赛,制定球队、球、裁判、球场等。
- 同时模拟一个或多个比赛。
- 球队策略(TeamStragy),比赛时决定球队的策略
这只是对系统的一个抽象形式,下图表示了系统中的类的多样性和它们之间的接连关系(“has”)。其中箭头表示了阅读的方向次序。游戏引擎(GameEngine)拥有若干比赛(Game);比赛(Game)有三个裁判、一个球、两支球队和一个球场;而球队又有多个球员和一个策略产生器。
Fig 1 - High level view
确定设计问题
现在你要决定
- 这些对象如何组织
- 如何创建
- 如何在设计说明书中确切地阐述当他们彼此影响时的行为。
首先,你得写下对足球引擎的最小描述来确定设计问题,例如下面是是对我们之前讨论的一对象的设计问题
- 足球(Ball)
- 当球的位置变化,所有的球员和裁判应当能够立即感知。
- 球队与球队策略(Team and TeamStrategy)
- 在比赛中,终端用户可以改变球队的策略(如从进攻改为防守)
- 球员(Player)
- 球队中的球员还得有一些额外的职责,如前锋、后卫等,应该可以在运行进指派这些职责。
- 球场(球场)
- 每一个球场要有座位、草皮、观众等,而且每一个球场都应该有不同的外观。
现在让我们想想该怎么确定模式以解决这些设计问题
确定要用的模式
再仔细看看(是的,最好多看几次)上面确定的设计问题,现在让我们想想怎么用设计模式来解决它们。
1: 解决与球(Ball)相关的设计问题
首先来看看关于球的说明,需要设计一个框架使得当球的状态(位置)变化时能够通知所有球员和裁判,以得到球的新状态(位置),实际上就是:
特定的设计问题:当球的位置变态,马上通知所有球员和裁判。
问题泛化:当主题(这里是指球)改变,所有的依赖物(在这里是指球员等)能够自动获得通知并更新。
当你遇到这样的设计问题,应当马上想起 GOF 模式,甚至立马认识到可以用Observer 模式来解决问题。
观察者模式(Observer Pattern):定义了对象间一对多的依赖关系,当一个对象的状态改变,自动通知所有依赖对象并更新。
在这里我们使用这个模式是因为当球的位置变化时需要通知所有的球员。
2: 解决与球队(Team)和球队策略(TeamStrategy)相关的设计问题
然后,我们来解决球队和球队策略的问题。像之前讨论的那样,当比赛进行时,终端用户能够改变他的球队的策略(如从进攻改为防守)。无疑地,这意味着我们需要把球队策略从球队中分离出来。
特定的设计问题:在比赛进行中终端用户能够改变它的球队的策略(例如从进攻改为防守)
问题泛化:使客户(在这里是球队)能够独立地改变算法(球队策略)
你可以选择 Strategy 模式来解决上面这个设计问题。
策略模式(Strategy Pattern):定义一系列算法,通过封装使它们可以互相替换,Strategy模式使用户能够独立地改变算法。
3: 解决与球员(Player)相关的设计问题
现在让我们来完成与球员相关的设计说明书。从我们的问题定义可以确定我们需要在运行时为每一个球员指派不同的职责(如前锋、后卫等)。这时候我们可以考虑子类化(也就是继承),通过创建一个球员类,然后从这个基类派生一些类,如前锋、后卫等。但它的不足是当你子类化的时候,你不能从对象的实现中分离职责。
换言之,在我们的案例中子类化并非恰当的方法,因为我们需要从球员的实现中分离类似前锋、中锋、后卫等职责。原因在于球员在某一时刻是前锋,而另一个时刻同一个球员又可以是中锋。
特定的设计问题:球队中的球员有额外的职责,如前锋、后卫等,而且要能够在运行时指派。
问题泛化:需要在对象(在这里是指球员)上动态附加额外职责(如前锋、中锋等),而且不可使用子类化。
那么你可以选择 Decorator 模式来解决这个设计问题。
装饰者模式(Decorator Pattern):在对象上动态地额外附加职责,Decorator 提供了子类化之外的灵活的扩展功能。
4: 解决球场(PlayGround)相关的设计问题
如果看去看看球场的说明,可以发现球场的外观由多个子单元(如座位、草皮和观众等)决定。球场的外观根据这些子单元的不同而不同,因此,我们需要特别的构建方式,它可以创建不同的球场。也就是说一个意大利球场应该有与英格兰球场不同的座位结构和草皮,但游戏引擎却可以通过调用相同的函数族来创建这些球场。
特定的设计问题:每个球场都由座位、草皮和观众等构成,但它们又有互不相同的外观。
问题泛化:需要从对象(球场)的表示(球场的外观)分离它的构建,还需要使用同样的构建过程来创建不同的表示。
创建者模式(Builder Pattern):从复杂对象的表示中分离它的构建,从而使相同的构建过程能够创建不同的表示。
现在,你可以选择 Builder 模式来解决上面的设计问题。
第二部分
解决方案架构师:我叫你去学学模式
愚蠢的开发者:是的,现在我可以用模式开发一个足球引擎了
解决方案架构师:啊?你的意思是?!@@#!
应用 Observer 模式
在这一节,我们先深入学习 Observer 模式,然后应用模式来解决第一个设计问题。不知道你还记不记得第一个设计问题:
- 当球的位置变化,马上通知所有的球员。
理解 Observer 模式
下面是 Observer 模式的是 UML 类图:
Fig 2 - Observer Pattern
下面介绍一下这个模式的成员:
- 主题(Subject)
Subject类提供了挂上和拆卸观察者的接口,并且持有一序列的观察者,还有如下函数:
- Attach - 增加一个新的观察者到观察者序列
- Detach - 从观察者序列中删除一个观察者
- Notify- 当发生变化时,调用每一个观察者的 Update 函数来通知它们。
- 具体的主题(ConcreteSubject)
这个类提供了观察者感兴趣的状态,它通过父类的 Notify 函数通知所有的观察者。ConcreteSubject的函数有:
- GetState - 返回主题的状态
- 观察者(Observer)
Observer类为所有的观察者定义了一个更新接口,用以接收来自主题的更新通知,它是一个抽象类,可以派生具体的观察者:
- Update - 这是一个抽象函数,具体的观察者会重载这个函数。
- 具体的观察者(ConcreteObserver)
这个类维护了一个主题的引用,用来在收到通知的时候接收主题的状态。
- Update - 这是具体类重载的函数,当主题调用它时,ConcreteObserver 调用主题的 GetState 函数来更新与主题状态相关的信息。
应用 Observer 模式
现在让我们来看看怎么用这个模式解决我们的特定问题,下图或许能给你一点启发:
Fig 3 - Solving Our First Design Problem
当调用球的 SetBallPosition 函数设置一个新的位置时,它马上调用类 Ball 中定义的 Notify 函数。Notify 函数迭代观察者序列,并调用它们的 Update 函数。当 Update 函数被调用,观察者就可以通过调用 FootBall 类的 GetBallPosition 函数来得到球的新的状态位置。
各部分详述如下:
Ball (Subject)
下面是类 Ball 的实现。
- ' Subject : The Ball Class
- Public Class Ball
- 'A private list of observers
- Private observers As new System.Collections.ArrayList
- 'Routine to attach an observer
- Public Sub AttachObserver(ByVal obj As IObserver)
- observers.Add(obj)
- End Sub
- 'Routine to remove an observer
- Public Sub DetachObserver(ByVal obj As IObserver)
- observers.Remove(obj)
- End Sub
- 'Routine to notify all observers
- Public Sub NotifyObservers()
- Dim o As IObserver
- For Each o In observers
- o.Update()
- Next
- End Sub
- End Class ' END CLASS DEFINITION Ball
FootBall (ConcreteSubject)
下面是类 FootBall 的实现。
- ' ConcreteSubject : The FootBall Class
- Public Class FootBall
- Inherits Ball
- 'State: The position of the ball
- Private myPosition As Position
- 'This function will be called by observers to get current position
- Public Function GetBallPosition() As Position
- Return myPosition
- End Function
- 'Some external client will call this to set the ball's position
- Public Function SetBallPosition(ByVal p As Position)
- myPosition = p
- 'Once the position is updated, we have to notify observers
- NotifyObservers()
- End Function
- 'Remarks: This can also be implemented as a get/set property
- End Class ' END CLASS DEFINITION FootBall
IObserver (Observer)
下面是类 IObserver的实现,它提供了具体的观察者(Concrete Observers)的接口。
- ' Observer: The IObserver Class
- 'This class is an abstract (MustInherit) class
- Public MustInherit Class IObserver
- 'This method is a mustoverride method
- Public MustOverride Sub Update()
- End Class ' END CLASS DEFINITION IObserver
Player (ConcreteObserver)
下面是类 Player 的实现,它继承自 IObserver:
- ' ConcreteObserver: The Player Class
- 'Player inherits from IObserver, and overrides Update method
- Public Class Player
- Inherits IObserver
- 'This variable holds the current state(position) of the ball
- Private ballPosition As Position
- 'A variable to store the name of the player
- Private myName As String
- 'This is a pointer to the ball in the system
- Private ball As FootBall
- 'Update() is called from Notify function, in Ball class
- Public Overrides Sub Update ()
- ballPosition = ball.GetBallPosition()
- System.Console.WriteLine("Player {0} say that the ball is at {1},{2},{3} ", _
- myName, ballPosition.X, ballPosition.Y, ballPosition.Z)
- End Sub
- 'A constructor which allows creating a reference to a ball
- Public Sub New(ByRef b As FootBall, ByVal playerName As String)
- ball = b
- myName = playerName
- End Sub
- End Class ' END CLASS DEFINITION Player
Referee (ConcreteObserver)
下面是类 Referee 的实现,它也继承自 IObserver
- ' ConcreteObserver : The Referee Clas
- Public Class Referee
- Inherits IObserver
- 'This variable holds the current state(position) of the ball
- Private ballPosition As Position
- 'This is a pointer to the ball in the system
- Private ball As FootBall
- 'A variable to store the name of the referee
- Private myName As String
- 'Update() is called from Notify function in Ball class
- Public Overrides Sub Update()
- ballPosition = ball.GetBallPosition()
- System.Console.WriteLine("Referee {0} say that the ball is at {1},{2},{3} ", _
- myName, ballPosition.X, ballPosition.Y, ballPosition.Z)
- End Sub
- 'A constructor which allows creating a reference to a ball
- Public Sub New(ByRef b As FootBall, ByVal refereeName As String)
- myName = refereeName
- ball = b
- End Sub
- End Class ' END CLASS DEFINITION Referee
类
Position同样的,我们需要一个位置类来表示球的位置
- 'Position: This is a data structure to hold the position of the ball
- Public Class Position
- Public X As Integer
- Public Y As Integer
- Public Z As Integer
- 'This is the constructor
- Public Sub New(Optional ByVal x As Integer = 0, _
- Optional ByVal y As Integer = 0, _
- Optional ByVal z As Integer = 0)
- Me.X = x
- Me.Y = y
- Me.Z = Z
- End Sub
- End Class ' END CLASS DEFINITION Position
组装起来
现在我们创建一个球和一些观察者,然后把观察者挂接到球上,这样在球的位置变化的时候就可以自动地通知它们。
- 'Let us create a ball and few observers
- Public Class GameEngine
- Public Shared Sub Main()
- 'Create our ball (i.e, the ConcreteSubject)
- Dim ball As New FootBall()
- 'Create few players (i.e, ConcreteObservers)
- Dim Owen As New Player(ball, "Owen")
- Dim Ronaldo As New Player(ball, "Ronaldo")
- Dim Rivaldo As New Player(ball, "Rivaldo")
- 'Create few referees (i.e, ConcreteObservers)
- Dim Mike As New Referee(ball, "Mike")
- Dim John As New Referee(ball, "John")
- 'Attach the observers with the ball
- ball.AttachObserver(Owen)
- ball.AttachObserver(Ronaldo)
- ball.AttachObserver(Rivaldo)
- ball.AttachObserver(Mike)
- ball.AttachObserver(John)
- System.Console.WriteLine("After attaching the observers...")
- 'Update the position of the ball.
- 'At this point, all the observers should be notified automatically
- ball.SetBallPosition(New Position())
- 'Just write a blank line
- System.Console.WriteLine()
- 'Remove some observers
- ball.DetachObserver(Owen)
- ball.DetachObserver(John)
- System.Console.WriteLine("After detaching Owen and John...")
- 'Updating the position of ball again
- 'At this point, all the observers should be notified automatically
- ball.SetBallPosition(New Position(10, 10, 30))
- 'Press any key to continue..
- System.Console.Read()
- End Sub
- End Class
运行
下面是运行程序的输出
结论
模式可以分为两类
- 关于目的
- 关于范围
其中关于目的又可以分为创建、结构和行为等三种,例如
- 我们刚才学习的 Observer 模式是一种行为模式(因为它有助于对行为建模和对象间的交互)
- 创建者模式则是一种创建型模式(因为它封装了如何以特别的方式创建对象)
下图是完整的分类图表
我希望这篇文章
- 可以让你理解设计模式
- 可以帮助你在项目中应用模式
- 在你跟朋友谈起模式的时候对你有所帮助
最后,如果你已经跃跃欲试(杰出程序员的特征之一),那么我向你推荐 Art Of Living 专题的第一部分(参考http://www.artofliving.org/courses.html)。这个交互式专题讨论分为 6 天,共 18 小时,希望它能够帮你找到工作与生活的平衡——既可以理清自己的思考,又可以增进生活质量。你可以从这里开始:http://www.artofliving.org/centers/main.htm。 blog原文地址http://blog.csdn.net/lanphaday/archive/2008/09/11/2915518.aspx
原文地址:http://www.codeproject.com/KB/cpp/applyingpatterns2.aspx
译者:赖勇浩(http://blog.csdn.net/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: 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 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
运行
程序运行后的输出如下:
第四部分
应用 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: 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
运行
下面是运行程序的输出
结论
在本文中我们讨论了
- 模式及其实现
- 模式及其实现
暂时就这么多了。事实上,正是 code project 社区对我前一篇文章的支持才鼓气我的勇气来发表这一篇。谢谢所有人的支持和鼓励!