回复人: San_Daniel(丹少爷) (2001-8-28 19:52:14) 得0分
这是我前一段写的文档,参考一下吧
3 总论
这个项目的开发目标是一个ActiveX控件,因而整个项目的核心是如何构造一个
符合ActiveX标准的类,使之能够完成需求分析书给定的功能。我们把最终提供
的产品类命名为 GobangGame,由它的名称可以看出,该类描述的是"五子棋游
戏"这一类事物。
现实生活中的经验告诉我们,一场五子棋游戏由以下几个要素构成:
两名游戏者,他们分别执黑、白棋
一名裁判,负责裁定比赛的胜负,以及适时的开始和结束比赛
一套具体规则,供裁判作为判定的依据
一个五子棋盘
两盒棋子
游戏者在游戏中的作用是在棋盘上落子,我们用一个抽象类Player来描述游戏者
的角色,它的主要接口方法如下:
playAStep():在棋盘上下一颗子
setSide():指定游戏者执黑或执白
对于计算机游戏者而言,落子之前要根据棋盘的形势,决定落子位置,然后完
成自己的任务--在棋盘上落子。而对于"真人"游戏者而言,事实上是用户的代
理,他应该按照用户的指示,在棋盘上指定位置处落子。这两类游戏者都兼容游
戏者的接口,并且在具体动作上有所区别,因而我们设计Player的两个子类
Computer和LocalUser,它们对Player有不同的扩展。
Computer
setAILevel():指定人工智能等级
LocalUser
setCommand():命令游戏者在指定位置处落子
至此,我们完成了对游戏者的描述,这时,应当注意到,裁判事实上也是一类特
殊的游戏者,但是他在游戏中所起的作用是判定胜负和控制比赛,有理由使他也成
为Player的一个子类,这时,我们修改Player抽象类的方法playAStep(),赋予它一个更
贴切的名称:act()每一个游戏者在游戏中轮流有act的机会,当裁判act的时候,他将
判断棋局,决定是否宣布某人获胜,或者因为某种原因中止比赛。而游戏者act的时
候可以选择在棋盘上落子或者干点别的。
很容易注意到,在上述抽象分析中,我们给予了五子棋组件最大的灵活性,可以
方便的对Player抽象类派生产生新的游戏者类型。
五子棋盘和棋子我们从简化程序的考虑,将它们合为一体,这套器具我们抽象为
一个抽象类ChessInfo,从这个命名中可以知道,对于棋局状况的记录也由这个类负
责。这样,ChessInfo中就必须包含已经走出的每一步棋,考虑到悔棋的需要,栈结
构是最合适不过的。这个抽象类的接口暂时做以下设计。
redraw():重绘棋盘,包括已经落下的棋子
showMessage(): 显示信息
addAStep():添加一步棋
removeLastStep():移除最后一步棋
clear():清除棋盘,清除对局状态
getPosStatus():查询指定位置的状态,是否落子,落的是哪一方的子
接下来,我们考虑一个智能化的棋具,能够主动的对棋盘形势做出分析,进而对
游戏者提供一定的帮助,比如:重要形势的报警。这样,我们需要增加一个重要的
接口:
analyse():分析棋局,提供参考数据
最后,我们考虑游戏规则,显然一套游戏规则要完成的唯一功能就是判定胜负,
因此我们设计一个抽象类Rule,接口如下:
judge():判定一个棋局,是否有人获胜,以及谁因为什么原因获胜。
getRule():查询一个具体的规则细则。
setRule():设置一个具体的规则细则。
在上述分析过程中,我们得到这样一组类:
Player
Computer
LocalUser
Umpire
ChessInfo
Rule
而最终产品GobangGame将组合上述类对象,这时,注意观察可以发现,上面的类
结构已经超出了"五子棋"的范畴,它事实上可以描述大多数棋类游戏!这些棋类游
戏的的差别只在于规则,于是我们从Rule中派生出GobangRule,并从ChessInfo中派生
出GobangChessInfo,专门用于我们的五子棋。
GobangGame的接口在需求分析书中有详细的描述,在这场游戏中,GobangGame
扮演着联系用户和各类对象之间的桥梁作用,接收用户的输入,对内部对象发出请
求,再将结果反馈给用户,如此周而复始。
接下来,我们讨论这些类横向的关系。
首先,作为一个Player,无论他是电脑或是用户甚至于裁判,他必须知道规则,因
而在Player类中,应该存在一个Rule的引用。同时,他必须了解棋局的状态,所以在
Player类中,还应该有一个ChessInfo的引用。
其次,棋局状态与外部情况无关,不须要知道任何人的存在,所以这是一个相对
单纯的类。
最后,规则要判断一个棋局,必须与该棋局相识,所以对judge的调用应该传递一
个棋局作为参数。
在具体设计中,又注意到一个问题,ChessInfo中包含了绘制的方法,而考虑到最终
产品的移植性,不应该将平台相关的绘制操作与平台无关的分析操作混在一起,因
而引入Bridge(设计模式4.2)模式将两部分分离,设计另一个ChessPainter类族用于实现
具体绘图操作,并被ChessInfo组合,合作完成ChessInfo的设计目标。
以下,描述如何将上述类的对象组合成为一个完整的程序。
我们的最终产品是一个ActiveX控件,因而很自然的想到使用ATL来实现GobangGame类。
可是,由于COM/ActiveX标准是Windows平台上的标准,同时使用ATL会带来很多平台相
关的代码,不利于未来的移植,所以,我们考虑实现一个平台无关的GobangGame类
和一个ATL的GobangGameShell类,两个类的接口大体上一致,用GobangGameShell实现
一些平台相关的代码。并且用户的输入首先被GobangGameShell接收,然后委托
GobangGame完成处理。这样,对于一切支持PUSH式程序的平台,都可以写这样一个
接收消息的Shell,从而保证了GobangGame的纯洁性。
一个GobangGame对象如何与其他对象协同工作呢?首先,GobangGame创建一个棋局
对象(ChessInfo),被所有参与游戏的游戏者共享。接下来创建两名游戏者(Player),用
户对接口属性的控制决定了这两名Player的实际类型(Computer或是LocalUser)。下一步
创建一名特殊的"游戏者"--裁判(Umpire)。最后按照用户对接口属性的控制创建一
套规则(Rule)。游戏者、裁判都应该与棋局对象以及规则相识,这样,整套游戏创
建完毕。
注意,上面没有提到ChessPainter对象的创建过程,因为ChessPainter的子类将是平台
相关的,我们不应该在纯洁的GobangGame中提到它们的名字,因而,在不纯洁的
GobangGameShell中创建ChessPainter对象(与具体的子类相联系)是正确的选择。我们
应该在GobangGame中设置一个setChessPainter()接口,用于接收创建好的ChessPainter对
象,并使用它来初始化ChessInfo。
当所有对象都被创建并正确初始化后,GobangGame要做的事就是接收用户的控制
(包括输入事件和属性、方法调用)。这是一类典型的PUSH程序样式,一组消息处
理函数是必不可少的。大致罗列如下:
onStandardClick() 主键点击,我们把鼠标左键点击事件广义化为这个事件,这样它还
可以描述其他输入条件下的同类事件。
onExtraClick() 附加键点击,我们把鼠标右键点击事件广义化为这个事件。
onRun() 得到运行机会。这个事件应该尽可能频繁的发生,因为每个游戏者只有在
得到运行机会的时候才能完成自己的职责。GobangGameShell可以将它挂在诸如时钟
中断之类的位置上。