原文地址:
http://www.codeproject.com/KB/architecture/applyingpatterns.aspx
作者:An 'OOP' Madhusudanan
译者:赖勇浩(http://blog.csdn.net/lanphaday )
译者说:这是一篇非常好的文章,有非常棒的例子,非常棒的文笔,非常棒的代码(VB.net编写的,但你肯定读得懂),如果你还不懂设计模式,那它肯定是最适合你的 DPs 文章之一。
第一部分
解决方案架构师:你可以尝试使用模式
愚蠢的开发者:好的,它像 ActiveX 控件那样用吗?"
介绍
关于本文
本文希望能够做到
- 以简单、可读的方式向你介绍模式
- 教你如何真正“应用”模式(模式易学,但必须有过硬的设计本领才能应用它们解决问题)
- 让你认清应用 Builder、Observer、Strategy和 Decorator(这几个可是少数极常用的模式)模式的时机。
- 展示如何用 Observer 模式解决设计难题
全文通过如下内容依次推进
- 为一个简单足球游戏引擎建立模型
- 确定足球游戏引擎中的设计问题
- 决定用哪些模式来解决设计问题
- 然后真正地利用 observer 模式来解决其中一个设计问题。
先决条件
代码使用指南
- 相应的 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类提供了挂上和拆卸观察者的接口,并且持有一序列的观察者,还有如下函数:
-
Attach - 增加一个新的观察者到观察者序列
-
Detach - 从观察者序列中删除一个观察者
-
Notify- 当发生变化时,调用每一个观察者的 Update 函数来通知它们。
这个类提供了观察者感兴趣的状态,它通过父类的 Notify 函数通知所有的观察者。ConcreteSubject的函数有:
Observer类为所有的观察者定义了一个更新接口,用以接收来自主题的更新通知,它是一个抽象类,可以派生具体的观察者:
-
Update - 这是一个抽象函数,具体的观察者会重载这个函数。
这个类维护了一个主题的引用,用来在收到通知的时候接收主题的状态。
-
Update - 这是具体类重载的函数,当主题调用它时,ConcreteObserver 调用主题的 GetState 函数来更新与主题状态相关的信息。
应用 Observer 模式
现在让我们来看看怎么用这个模式解决我们的特定问题,下图或许能给你一点启发:
Fig 3 - Solving Our First Design Problem
当调用球的 SetBallPosition 函数设置一个新的位置时,它马上调用类 Ball 中定义的 Notify 函数。Notify 函数迭代观察者序列,并调用它们的 Update 函数。当 Update 函数被调用,观察者就可以通过调用 FootBall 类的 GetBallPosition 函数来得到球的新的状态位置。
各部分详述如下:
Ball (Subject)
下面是类 Ball 的实现。
-
-
PublicClassBall
-
-
PrivateobserversAsnewSystem.Collections.ArrayList
-
-
PublicSubAttachObserver(ByValobjAsIObserver)
- observers.Add(obj)
-
EndSub
-
-
PublicSubDetachObserver(ByValobjAsIObserver)
- observers.Remove(obj)
-
EndSub
-
-
PublicSubNotifyObservers()
-
DimoAsIObserver
-
ForEachoInobservers
- o.Update()
-
Next
-
EndSub
-
EndClass
FootBall (ConcreteSubject)
下面是类 FootBall 的实现。
- 'ConcreteSubject:TheFootBallClass
- PublicClassFootBall
- InheritsBall
- 'State:Thepositionoftheball
- PrivatemyPositionAsPosition
-
'Thisfunctionwillbecalledbyobserverstogetcurrentposition
- PublicFunctionGetBallPosition()AsPosition
- ReturnmyPosition
- EndFunction
-
'Someexternalclientwillcallthistosettheball'sposition
- PublicFunctionSetBallPosition(ByValpAsPosition)
- myPosition=p
-
'Oncethepositionisupdated,wehavetonotifyobservers
- NotifyObservers()
- EndFunction
-
'Remarks:Thiscanalsobeimplementedasaget/setproperty
- EndClass'ENDCLASSDEFINITIONFootBall
IObserver (Observer)
下面是类 IObserver的实现,它提供了具体的观察者(Concrete Observers)的接口。
-
-
-
PublicMustInheritClassIObserver
-
-
PublicMustOverrideSubUpdate()
-
EndClass
Player (ConcreteObserver)
下面是类 Player 的实现,它继承自 IObserver:
-
-
-
PublicClassPlayer
-
InheritsIObserver
-
-
PrivateballPositionAsPosition
-
-
PrivatemyNameAsString
-
-
PrivateballAsFootBall
-
-
PublicOverridesSubUpdate()
- ballPosition=ball.GetBallPosition()
-
System.Console.WriteLine("Player{0}saythattheballisat{1},{2},{3}",_
- myName,ballPosition.X,ballPosition.Y,ballPosition.Z)
-
EndSub
-
-
PublicSubNew(ByRefbAsFootBall,ByValplayerNameAsString)
- ball=b
- myName=playerName
-
EndSub
-
EndClass
Referee (ConcreteObserver)
下面是类 Referee 的实现,它也继承自 IObserver
-
-
PublicClassReferee
-
InheritsIObserver
-
-
PrivateballPositionAsPosition
-
-
PrivateballAsFootBall
-
-
PrivatemyNameAsString
-
-
PublicOverridesSubUpdate()
- ballPosition=ball.GetBallPosition()
-
System.Console.WriteLine("Referee{0}saythattheballisat{1},{2},{3}",_
- myName,ballPosition.X,ballPosition.Y,ballPosition.Z)
-
EndSub
-
-
PublicSubNew(ByRefbAsFootBall,ByValrefereeNameAsString)
- myName=refereeName
- ball=b
-
EndSub
-
EndClass
类 Position
同样的,我们需要一个位置类来表示球的位置
-
-
PublicClassPosition
-
PublicXAsInteger
-
PublicYAsInteger
-
PublicZAsInteger
-
-
PublicSubNew(OptionalByValxAsInteger=0,_
-
OptionalByValyAsInteger=0,_
-
OptionalByValzAsInteger=0)
-
Me.X=x
-
Me.Y=y
-
Me.Z=Z
-
EndSub
-
EndClass
组装起来
现在我们创建一个球和一些观察者,然后把观察者挂接到球上,这样在球的位置变化的时候就可以自动地通知它们。
-
-
PublicClassGameEngine
-
PublicSharedSubMain()
-
-
DimballAsNewFootBall()
-
-
DimOwenAsNewPlayer(ball,"Owen")
-
DimRonaldoAsNewPlayer(ball,"Ronaldo")
-
DimRivaldoAsNewPlayer(ball,"Rivaldo")
-
-
DimMikeAsNewReferee(ball,"Mike")
-
DimJohnAsNewReferee(ball,"John")
-
- ball.AttachObserver(Owen)
- ball.AttachObserver(Ronaldo)
- ball.AttachObserver(Rivaldo)
- ball.AttachObserver(Mike)
- ball.AttachObserver(John)
-
System.Console.WriteLine("Afterattachingtheobservers...")
-
-
-
ball.SetBallPosition(NewPosition())
-
- System.Console.WriteLine()
-
- ball.DetachObserver(Owen)
- ball.DetachObserver(John)
-
System.Console.WriteLine("AfterdetachingOwenandJohn...")
-
-
-
ball.SetBallPosition(NewPosition(10,10,30))
-
- System.Console.Read()
-
EndSub
-
EndClass
运行
下面是运行程序的输出
结论
模式可以分为两类
其中关于目的又可以分为创建、结构和行为等三种,例如
- 我们刚才学习的 Observer 模式是一种行为模式(因为它有助于对行为建模和对象间的交互)
- 创建者模式则是一种创建型模式(因为它封装了如何以特别的方式创建对象)
下图是完整的分类图表
我希望这篇文章
- 可以让你理解设计模式
- 可以帮助你在项目中应用模式
- 在你跟朋友谈起模式的时候对你有所帮助
最后,如果你已经跃跃欲试(杰出程序员的特征之一),那么我向你推荐 Art Of Living 专题的第一部分(参考http://www.artofliving.org/courses.html)。这个交互式专题讨论分为 6 天,共 18 小时,希望它能够帮你找到工作与生活的平衡——既可以理清自己的思考,又可以增进生活质量。你可以从这里开始:http://www.artofliving.org/centers/main.htm。
历史
- “历史能让你认识到生活不过是一场戏”
- 2005年11月7日,准备发布这篇文章
分享到:
相关推荐
在本教程中,我们将探索如何设计一个足球引擎,并利用四个重要的设计模式——观察者模式、装饰者模式、策略模式和构建者模式。这些模式是软件工程中的核心概念,能够提高代码的灵活性、可维护性和可扩展性。我们将...
1. **Android SDK和环境搭建**:首先,疯狂足球应用基于Android SDK开发,需要在Android Studio中进行项目构建。开发者需要熟悉SDK Manager的使用,下载并配置相应的Android版本以及构建工具。 2. **项目结构**:...
开发者可能使用了一些常见的设计模式,如单例模式(用于管理全局状态,如游戏计时器)、工厂模式(用于创建不同的游戏对象)以及观察者模式(用于事件驱动,如玩家操作的响应)。 游戏的音效和音乐可能通过HTML5的...
【标题】"安卓Android源码——体育游戏-疯狂足球...对于经验丰富的开发者,这个源码可以作为灵感来源,借鉴其中的设计模式和最佳实践。总的来说,这个“疯狂足球”源码是Android游戏开发学习的重要素材,值得深入研究。
1. **游戏架构设计**: - 游戏通常采用Model-View-Controller (MVC) 或者 Model-View-Presenter (MVP) 架构。疯狂的足球源码中可能采用了某种架构模式,帮助我们理解游戏的模块划分。 2. **Android SDK**: - ...
这个项目可能由一个名为"Football-main"的主程序组成,这通常是一个可执行文件或者源代码文件,它是整个应用程序的核心部分。下面将详细讨论与这个主题相关的Linux、C语言编程以及软件开发的知识点。 1. **Linux...
**第一部分:导论** 1. **FM和TT&F战术思想的演变** - 在早期的FM版本中,有一种名为Diablo的战术非常流行,其核心是利用游戏引擎的漏洞,通过三名中场球员的配置,让中间的MC(中场球员)在无人防守的情况下接球...
\n\n游戏策划:游戏策划是游戏开发的第一步,包括确定游戏类型、游戏情节、技术呈现、操作方式等。体育类游戏通常以模拟真实比赛为主,游戏情节设计相对简单,重点在于游戏流程的规划和不同场景的设计。目标平台为...
【标题】中的“fshows-java-table-football”是一个项目名称,暗示了这是一个使用Java语言开发的项目,主要用于组织和管理首展公司第一届技术部的桌上足球比赛分组。"哈哈"可能是开发者或团队的一种轻松幽默的表达,...
1. **疯狂足球**:这个案例展示了如何在Android平台上构建一个动态且引人入胜的体育竞技游戏。开发者可以从中学习到如何处理游戏逻辑、物理模拟(如球的运动轨迹)、碰撞检测以及用户交互设计。同时,它还涵盖了游戏...
TT&F09是该系列的第六代,起源于对FM06比赛引擎的深入研究,最初是为了测试和反驳关于游戏引擎存在严重问题的观点。随着时间的推移,TT&F不断成熟,增加了更多的战术策略和分析,吸引了大量知识丰富的玩家参与其中...
1. 无纸贸易的运行模式包括点对点模式、单一窗口模式、外联网模式,但不包括一对多模式。无纸贸易通过减少纸质文件的使用,提高交易效率和透明度。 2. 网站设计中,一般建议网站的标准色彩不超过三种,以保持视觉...
C#是微软开发的一种面向对象的编程语言,广泛用于Windows平台上的应用程序开发,包括桌面应用、移动应用以及游戏开发,尤其在Unity引擎中,C#是首选的脚本语言,用于创建3D和2D游戏。 基于以上信息,我们可以深入...
1. JavaScript简介:JavaScript是一种轻量级的解释型编程语言,主要用于网页和网络应用开发。在CSE104-HeadFootball项目中,JavaScript负责处理游戏逻辑、用户交互以及画面更新。 2. 游戏框架选择:为了简化游戏开发...
例如,你可以制作一款只允许头球进球的比赛模式,或者设计一辆具有特殊技能的新车。 **Unreal Engine C++** RLForge 支持使用C++进行开发,这是虚幻引擎的主要编程语言之一。C++提供了直接访问引擎底层的能力,使得...
Canvascape是一个概念验证项目,旨在展示如何使用`<canvas>`和JavaScript实现一个简单的第一人称射击游戏(FPS)。该项目包括有纹理和无纹理两个版本,通过精细的纹理处理和场景构建,展现了`<canvas>`元素在创建逼真...
1. **雅虎**:作为互联网先驱,雅虎是早期的搜索引擎和门户网站,提供新闻、邮件、搜索服务等。在互联网发展初期扮演了重要角色,后来经历了多次转型和并购,包括与Verizon的合并。 2. **瑞典宜家家居公司(IKEA)*...