杀人游戏系列 之二

在第一篇中,我们已经引出了话题。在经过初步的分析之后,整个team的思路很明确。从业务逻辑入手,兵分两路,一路向ui开进,一路向database开进。而关键的部分就是中间实体类的设计。

1,模型的重构
在第一篇中,我们已经确定了基本的模型。但是,这个模型准确嘛?无论是平民,还是杀手,以及警察,他们只是游戏中的一个角色。而这个角色真实的身份,应该是用户。这样,先把用户类抽提出来。如下。大家需要注意的是,这里的类图,我是拿OR designer画的。先暂且不管每个property是什么类型的。这个类包含一个user最基本的信息。userid是用来标志他的唯一编号。其中isonline来存储这个用户的状态。winnum和playnum分别是他赢的次数和玩的次数。

在有了user类后,下一个没有争议的类就该是game了。它的定义如下

gameno是每个游戏的唯一标志。这其中还定义了开始时间,结束时间。当然,还定义了谁最终赢得了game,以及平民,杀手,警察的数量。这两个实体,是本程序中,最基本的实体。也能准确体现现实世界中对象。那么,针对一个user,只有把他放在特定的game中,他才能算是一个玩家。如果,他不加入到game中,那他就不算玩家,而只能算是一个user。有的user是平民,有的杀手,而又有的是警察。那又该如何体现user在game中的角色呢?思路有这么两个,第一,从user继承,分别写平民,杀手等类。第二,单独创建一个player类。针对第一个思路,在创建对象的时候,需要new出不同的类,而且在判断user的身份时,需要调用反射,看是什么类的对象才知道。更麻烦的是,如何把它向数据库方向推呢?这个思路写出的代码很容易懂,却也有它的弱点。不知道它弱点的人,就无法使用好它。第二个思路比较简单,切实可行。player类的定义如下。

这个类里,定义了userid和gameno,这样,就很容易和game和user做关联。isactive用来保存这个player是否还活着。roleid则是其身份的标志。deadactivityno是他在那次活动中死去(先不说这个)。这里,roleid作为一个property来判断玩家的身份,避免了不同的类需要反射的问题。更重要的是,这样的设计很容易推向数据库。那roleid 的值实际上被限定在某个集合内。这个集合被定义为枚举类型。如下:
    public enum RoleFlag : int
    
{
        Civilian 
= 1,
        Killer 
= 2,
        Police 
= 4
    }
这里为1,2,4是方便后来的位运算。并没有其他的特殊设置。 这样,最基本的模型确定了。紧跟着需要做的,就是设计各个class中的property一系列的属性了。如图

这是gameno的属性设置。这里它被设成主键。还有,它在clr中的type为int,在server端的data type 为int not null identity。也就是int的自增型。在这里,其他的都好设置,唯独Server Data Type需要设计者对数据库和dbml格式特别熟悉。这也是为什么笔者一直带着大家熟悉dbml。把各个类中的字段的属性设置后,下一步就是建立关系了。
这个模型也很容易懂。一个user在不同时间可以加入不同的game,一个game可以拥有多个user。那这样,正好用player这个实体类将他们联系起来。就是一个user在不同时间,可以扮演不同角色,一个game可以拥有多个角色。那针对player中的roleid这个字段,它只有三个值,又该如何在数据库中体现它呢?大家都知道,数据库中,对于枚举型的,也只有把它们存在一个特殊的表里。然后,把这个表用相关的字段和另一个表的相关字段建里关系就可以了。这样,又从RoleFlag抽像出一个叫Role实体类来。用如下的功能建立关系,右击game类,选择add->Association,

这里把各个实体的关系建立起来。选择合适的字段。需要注意的是,别把parent和child搞反了。这样,整个模型如下:


2 业务逻辑中的对象抽提
单纯从client和server角度讲,它们是通过消息通信的。在本项目中ui 端的所有的对象,包括平民和杀手,他们之间所有的动作,比如,杀人,发言和投票,其实也都是通过消息机制发回server端。如果从这个角度来考虑的话,无论是杀人,投票和发言,均可以理解为一个消息。只是这个消息有三种罢了。这样,就有Activity实体类定义了这个消息体。又定义了枚举
    public enum ActivityTypeFlag : int
    
{
        Vote 
= 1,
        Kill 
= 2,
        Investigate 
= 4
    }
Activity实体类定义如下

Activity实际是定义的一个消息体。在这里,team发生了争执。boler主张在每个Activity中,可以允许一轮投票可以无法确定结果,不得不重投。Activity可以理解为某一次活动,比如一轮发言,一轮投票,等。按boler的主张,当某次投票决定谁死时,有可能会出现得票相同的结果,然后,不得不重投,然后用RoundCount保存是一共有几次重复。最终,boler在会议上说服了大家。这样,又从Activity,引申出Vote实体类。Activity 和Vote是1 :n的关系。而在一个game中,可以有多次Activity。这样game和Activity的关系为1 :n.  Vote类的定义如下

在这里,RoundNo是第几次。也就是一轮活动中,vote动作有几次重复。Voter是投票人,而Candidate是投给谁。这样,又可以通过这两个键与user类发生关系。综合以上的分析,整个模型为


3 业务层与对象的分离
在通常的思路中,每一个动作,都是由对象发出的。比如,杀人,投票。而在这里,team经过讨论,把业务逻辑彻底的从对象中剥离出来。将业务逻辑做成一个个的静态的方法。这样,对象只是业务逻辑的参与者,而不是发出者。这样,可以把所有的业务逻辑放在一起。这样,有利于检查业务逻辑是否有出错。boler已经完成了业务逻辑流程图。如下:

4 结语。在完成实体类的设计后,Tom发现上海的城市沙滩是今年最后一天营业。他匆匆完成设计,径自去沙滩享受他的假期去了。而这只是实体类。大家都知道,和数据库相关的项目,数据库的配制和部署,依然是很麻烦的事情。那么Tom又该如何完成下面的工作呢?他又为什么这么放心的走呢?peter的工作进展怎么样了呢?请看下篇。

上一篇
杀人游戏系列 之三 提供游戏代码下载
杀人游戏系列 之二
杀人游戏系列 之一

其他:
Linq To Sql进阶系列 -目录导航
C# 3.0入门系列-目录导航

posted @ 2007-10-15 18:38 Tom Song 阅读(3279) 评论(24) 编辑 收藏

 回复 引用 查看   
#1楼[楼主]2007-10-15 18:51 | 宋国安      
大家有不同的思路,畅所欲言啊。
 回复 引用 查看   
#2楼2007-10-15 19:24 | 金色海洋(jyk)      
好复杂呀。
 回复 引用 查看   
#3楼2007-10-15 19:52 | 补丁      
我想先玩游戏再说....做完了没?
 回复 引用 查看   
#4楼2007-10-15 19:55 | today      
思路非常的清晰,写得不错。
不过,感觉这样的设计更倾向于数据库设计而不是面向对象的设计了。

 回复 引用 查看   
#5楼2007-10-15 20:18 | yunhuasheng      
Very good,I will look this object!!
 回复 引用 查看   
#6楼2007-10-15 21:53 | Adrian H.      
@today
把这些实体类再Wrap到具体的业务对象类中,添加行为方法不就更OO了。。但有时候并没有必要那么做。。这个项目的问题域没有复杂到需要那么做。

@宋国安
有必要把Role分离到一个表中么?Role和程序逻辑是紧密结合的,Role种类应该不需要改变。如果仅仅是用来控制角色的Name,为何不到表现层去做呢?
虽然这个问题有些偏离这个系列的主题。。

 回复 引用   
#7楼2007-10-15 22:39 | 股吧[未注册用户]
很不错
 回复 引用   
#8楼2007-10-16 00:20 | 天黑请闭眼[未注册用户]
按boler的主张,当某次投票决定谁死时,有可能会出现得票相同的结果,然后,不得不重投

什么跟什么啊 你们有没有玩过杀人游戏 平票是要PK的人 然后大家重新投票 如果在次平票 需要PK以外的人 轮流发言 然后大家(除平票的人)在投 如果还相等 则宣布平局 真正的游戏规则是这样的 也是需求 游戏规则还不懂了就写杀人游戏 真是的
警察过!

 回复 引用   
#9楼2007-10-16 00:27 | 天黑请闭眼[未注册用户]
我早就想做一个杀人游戏了 业务上已经很熟了 因为每周都会去杀人吧玩杀人
只是技术上差些 网络方面的技术知识还不行 想有人与我合作做一套 我负责数据层 和业务逻辑 界面表示 对方负责技术支持 呵呵

 回复 引用 查看   
#10楼2007-10-16 08:25 | BlackCat      
谁能告诉我杀人游戏最吸引人的地方在哪 ?
 回复 引用 查看   
#11楼[楼主]2007-10-16 10:16 | 宋国安      
@补丁
做完了。等到最后,拿出来给你自己去修改哦。
@BlackCat
应该是它的游戏氛围吧。有点紧张,刺激。我并没有玩过几次,也说不太好。

 回复 引用 查看   
#12楼[楼主]2007-10-16 10:42 | 宋国安      
@today
可能是把所有的函数都从类里剥离了,然后给你这感觉吧。这里的类是用or designer设计的,并且在设计的过程中在不停向数据库靠近。总会有这感觉的。
@Adrian H.
我来阐述下我的观点吧。针对player类中的roleid这个file,传统的方法可以把它的类型定义为一个枚举。但是,在linq to sql中,只能是c#语言自己的基本类型。这样,roleid就被定义成int。而这样就没有办法限制它的值。把role分离到一个表中,通过关系,可以限制roleid的值。这里是linq to sql的软肋。

第二,按你的方法,是可以实现,而且没有任何瑕疵。但是,单纯从数据库的角度来讲,这个数据是不完整的。至少缺了东西。数据库的设计者会感觉到它的不完美。

 回复 引用 查看   
#13楼[楼主]2007-10-16 10:46 | 宋国安      
@天黑请闭眼
真被你说着了。我只是在team 会议上玩过一次。而后再也没有玩过。但是,我并不认为这里有什么问题。游戏的规则是人制定的。遵循什么样的规则,只要参加游戏的人都按这个规则就可以了。就如同扑克,不同的规则诞生出不同的玩法,有升级,有斗地主。
我们对游戏进行了简化,只为了加快开发速度。并且一开始的时候,就人为的限定了它的规则。这里会有可能和你玩的不一样。我觉得,这都是正常的。毕竟,我们只是在做着玩,并不是真要去开发一个什么杀人游戏。

 回复 引用 查看   
#14楼2007-10-16 18:14 | 伍迷      
首先得说基本都能看得懂,写得很好的,但有些细节需要再请补充一下。

1、“RoundNo是第几次。也就是一轮活动中,vote动作有几次重复。Voter是投票人,而Candidate是投给谁。这样,又可以通过这两个键与user类发生关系”
这里并没有说清楚两者是什么关系,用哪个属性来进行关联。

2、整个业务逻辑是一个完整解决方案中的一个Class Library吧,假设是BusinessLogicLayer,而里面的图是否是在一个Linq to Sql Classes上,即dbml上所绘制的?

3、各个class中的property一系列的属性是否是我们自己设置,还是下一讲你来指明?

4、RoleFlag 和ActivityTypeFlag 是你提到的两个枚举,不知应该保存在何处?

 回复 引用 查看   
#15楼2007-10-16 21:12 | Boler Guo      
1、User类中的UserID是主键,Voter和Candidate都是外键
2、这个业务逻辑被分为好几个Project(其实我反对过度分层)图是在dbml上画的
3、或者在类图中都设计好来生成数据库中的比较结构,也可以设计好表结构再生成类图
4、RoleFlag 对应表中的RoleID栏位,ActivityTypeFlag对应表中的ActivityTypeID(其实我反对搞成1、2、4这种Flag,用0、1、2表示多好)

 回复 引用 查看   
#16楼[楼主]2007-10-16 22:14 | Tom Song      
@小菲

快来看,当team中出现分歧时,这时就需要一个强有力的leader把大家拉拢到同一条思路上。

 回复 引用 查看   
#17楼2007-10-17 13:58 | 伍迷      
@Boler Guo
从你回答的第三条,我的感觉这样的设计还是以数据库设计为中心的方案。也就是说,我所做的业务逻辑都是需要与数据库中的表对应的。
如果是以数据库设计为中心,sql表结构->dbml容易,dbml->sql表结构不方便。原因在于我既要考虑.net数据类型,还要考虑sql数据类型。
如果还是以业务逻辑设计为中心的话,如枚举,继承等OO要素都应该在业务逻辑图中体现,比如用户可分为杀手、平民、警察等,这是一种继承结构。至于对应到数据库中是一张表,还是多张表,那是另当别论的,不应在这考虑。在做业务逻辑分析时,是不应该考虑表应该如何建的细节问题的。

 回复 引用 查看   
#18楼2007-10-17 14:05 | 钱彦云      
不错,看了有很多感想,由于评论不能贴图,就新写了一个完整的评论当作随笔发了。链接如下。再次感谢楼主能分享自己的经验
http://www.cnblogs.com/lodestar/archive/2007/10/17/927499.html

 回复 引用 查看   
#19楼[楼主]2007-10-17 14:31 | Tom Song      
@伍迷
是的,在第一稿中,就是采用的继承的方式,来确定杀手、平民、警察类, 但是,这个方式很快就被否决了. 因为,如果不用继承,一样可以实现,只是加个判断身份的property 而已. 因为继承要麻烦. 比如,任何用户都有可能成为杀手、平民、警察, 那就需要去new 出不同的类. 这比一个类复杂的多.

第二,在设计的时候, 是从类向数据库方向去推的. 确实存在你说的dbml->sql表结构不方便, 更好的方法,是业务逻辑人员确定最基本的类,而数据库人员再次修改以能把类向数据库方向推.

 回复 引用 查看   
#20楼2007-10-17 16:12 | 伍迷      
@Tom Song
你说得很对,就本例而言,继承的使用可能确实是不需要。因为这个例子应该不存在新用户角色的扩展问题。

如果存在这种扩展,这样的设计就有问题了,比如增加法官角色,法官有特殊于平民和杀手的属性,用继承就可以完美的实现抽象用户类的共享和新功能子类的扩展。

这里我的确很关心在linq中用面向对象设计的业务逻辑,其中包含很多组合、聚合、继承等关系,用到很多设计模式,比如N多的算法类、状态类、工厂类的业务层,如何通过linq的设计转到sql中。因为这也是传统ORM最困难的地方。

 回复 引用 查看   
#21楼[楼主]2007-10-17 18:49 | Tom Song      
@钱彦云
仔细看了下你的回复,或许,你的思路更接近与传统的oo设计。在这里,我们把业务逻辑彻底的从这些对象中剥离出来。因为,牵扯到client和server的关系,所以,业务逻辑实际上是在与client和server相关的类中完成的。

 回复 引用 查看   
#22楼[楼主]2007-10-17 18:51 | Tom Song      
@伍迷
我在和peter一起工作,也在思考该怎么写wcf 。或许等那个时候,你能看到项目全貌的时候,我想,你能体会为什么我们不用继承。

 回复 引用 查看   
#23楼2007-10-18 09:13 | 伍迷      
@Tom Song
期待ing

 回复 引用 查看   
#24楼2007-11-26 20:18 | AndyDavis      
挺好的,根据实例来学习设计过程。
关注。。。