兴国安邦

C# 3.0, Linq, Linq To Sql

博客园 首页 新随笔 联系 订阅 管理
  33 Posts :: 0 Stories :: 526 Comments :: 51 Trackbacks
在第一篇中,我们已经引出了话题。在经过初步的分析之后,整个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 on 2007-10-15 18:38 Tom Song 阅读(2342) 评论(24)  编辑 收藏 所属分类: C# 3.0Linq To Sql

Feedback

#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 股吧 [未注册用户]
很不错
  回复  引用    

按boler的主张,当某次投票决定谁死时,有可能会出现得票相同的结果,然后,不得不重投

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

我早就想做一个杀人游戏了 业务上已经很熟了 因为每周都会去杀人吧玩杀人
只是技术上差些 网络方面的技术知识还不行 想有人与我合作做一套 我负责数据层 和业务逻辑 界面表示 对方负责技术支持 呵呵
  回复  引用    

#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      
挺好的,根据实例来学习设计过程。
关注。。。
  回复  引用  查看