杀人游戏系列 之二

在第一篇中,我们已经引出了话题。在经过初步的分析之后,整个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 阅读(...) 评论(...) 编辑 收藏