原文地址:
http://www.codeproject.com/KB/architecture/applyingpatterns.aspx
作者:An 'OOP' Madhusudanan
译者:赖勇浩(http://blog.csdn.net/lanphaday )
译者说:这是一篇非常好的文章,有非常棒的例子,非常棒的文笔,非常棒的代码(VB.net编写的,但你肯定读得懂),如果你还不懂设计模式,那它肯定是最适合你的 DPs 文章之一。
解决方案架构师:你可以尝试使用模式
愚蠢的开发者:好的,它像 ActiveX 控件那样用吗?"
本文希望能够做到
全文通过如下内容依次推进
先决条件
代码使用指南
即使对设计模式知之甚少,设计师和开发者也会倾向于重用类和对象间来简化设计过程。简言之就是“设计模式考虑了多种对象(类、关系等)间的协作”,为常见的设计问题提供解决方案。最为重要的是他们为设计师和程序员提供一些“行话”来谈论他们的设计。例如你可以告诉你的朋友你使用了 Builder 模式来解决你项目中的一些问题。
Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides[即知名的四人帮(GOF)]为常见的设计问题提供了一致的分类模式。GOF 模式被认为是其它所有模式的基础。
使用模式的基本原则是可重用性。如果你正确理解了以模式为中心的软件工程概念,当遇到问题时你就不会重复发明轮子。这里有一些关于设计模式的重要观点:
真正的动手的经验可以给你更好的理念。
假设你在一家游戏开发公司供职,上头决定让你为公司的重要项目——足球游戏引擎做一套解决方案架构(很棒,哈哈)。现在由你领导设计整个足球游戏引擎,突然你就多了许多要考虑的事情,比如:
首先,需要标识游戏引擎中的所有对象。因此你要想像一下终端用户将如何使用这个系统,现在假设终端用户将用以下序列来操作游戏(先简单化):
系统是可能有若干个球场(PlayGrounds)和球队(Teams)。系统中实际上起码有这些对象:
另外,游戏引擎中还有一些逻辑对象,如:
这只是对系统的一个抽象形式,下图表示了系统中的类的多样性和它们之间的接连关系(“has”)。其中箭头表示了阅读的方向次序。游戏引擎(GameEngine)拥有若干比赛(Game);比赛(Game)有三个裁判、一个球、两支球队和一个球场;而球队又有多个球员和一个策略产生器。
Fig 1 - High level view
现在你要决定
首先,你得写下对足球引擎的最小描述来确定设计问题,例如下面是是对我们之前讨论的一对象的设计问题
现在让我们想想该怎么确定模式以解决这些设计问题
再仔细看看(是的,最好多看几次)上面确定的设计问题,现在让我们想想怎么用设计模式来解决它们。
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 模式的是 UML 类图:
Fig 2 - Observer Pattern
下面介绍一下这个模式的成员:
Subject类提供了挂上和拆卸观察者的接口,并且持有一序列的观察者,还有如下函数:
这个类提供了观察者感兴趣的状态,它通过父类的 Notify 函数通知所有的观察者。ConcreteSubject的函数有:
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
运行
下面是运行程序的输出
结论
模式可以分为两类
其中关于目的又可以分为创建、结构和行为等三种,例如
下图是完整的分类图表
我希望这篇文章
最后,如果你已经跃跃欲试(杰出程序员的特征之一),那么我向你推荐 Art Of Living 专题的第一部分(参考http://www.artofliving.org/courses.html)。这个交互式专题讨论分为 6 天,共 18 小时,希望它能够帮你找到工作与生活的平衡——既可以理清自己的思考,又可以增进生活质量。你可以从这里开始:http://www.artofliving.org/centers/main.htm。
历史