关于某道C#上机题的OO 我也来个OO设计吧,大家鉴赏鉴赏

题目

17个人围成一圈,从第一个人开始报数,报到3的倍数的退出,一直到剩下最后一个人,用面向对象的思想去做这道题。

需求分析

题目可抽象为N个游戏参与者(拟人,可非人。指任何参与游戏的物体,例如机器人等)根据某个规则玩游戏。解法要求为使用面向对象思想。

题目并未对输出进行要求,属于未定因素。但一般来讲,是游戏就要有结果,而我们需要将结果输出出来,才能知道游戏已结束。所以结果输出为必要条件,输出格式与详细程度为可变条件。

既然题目要求使用面向对象思想,在我的理解,就是要在应对变化时提高程序代码的复用率(继承),减少程序代码的修改率(尽量为添加,而非修改),并在这一条件要求下,尽可能提供便利的程序的使用方式(运行时代码组织方式)。

题目很短,简单分析即可得知:

题目可能产生的变量为:

  1. 1. 参与游戏物体的个数(本题为17)
  2. 2. 参与游戏物体的形式(本题为人)
  3. 3. 参与游戏物体的组织形式(本题为“围成一圈”)
  4. 4. 游戏规则
  5.   4.1 游戏起始位置(本题为“从第一个人开始”)
  6.   4.2 游戏形式(本题为“报数”)
  7.   4.3 游戏规则(本题为“报到3的倍数退出”)
  8.   4.4 游戏结束标志(本题为“一直到剩下最后一个人”)
  9. 5. 游戏结果输出形式

     

经过以上分析,我们基本在脑中已经大概有了对象组织结构的概念了,我们应该有Source类,来代表参与游戏的物体,应该有Game类来代表游戏本身等等。我们现在就开始组织这些零碎的观点,形成一个高可伸缩性的代码结构。

架构设计

根据需求分析,我们很容易得出一些想当然的结论,这个题目比较简单,我们就定义一个类代表人(Person),一个类代表游戏(Game),游戏类有两个方法,开始(GameStart)和结束(GameOver),不就可以了吗?

问题1:如何改变游戏物体的个数

简单,创建Person对象,然后加入到Game里去嘛,循环17次。所以,游戏需要多两个方法,添加,删除(Add,Remove)

问题2:如何改变游戏物体的类型(人或其他)

简单,定义一个基类(SourceBase),表示一个“游戏物体”好了。

问题3:如何表示“围成一圈”

简单,人嘛,围成一圈自然是手拉手了,所以人要有两个属性“左手(LeftHand)”和“右手(RightHand)”,分别指向上一个人和下一个人。

问题4:更换“游戏规则”

恩。。。Game类自身的逻辑就是游戏规则吧。如果要换一个规则,就重新写一个Game类。

问题5:如何输出结果?

简单,在GameOver的时候输出结果就行了嘛。。。恩。。所以GameStart和GameOver应该为两个事件。分别在游戏开始和结束时被触发,并输出相应的结果。

问题6:如果要在执行的每一步都输出结果呢?

恩。。。改变Game类中的逻辑吧(Game类:汗,又要改我。。。我为什么要说又呢?)

问题7:如果我要处理的不是人(没有左右手),而是一个怪物(只有一只手)?

晕。。。那就在创建一个怪物对象,只有一只手的!。。。等等。。好像添加删除的逻辑也要改。。(GAME类:大哥别改了。。服了。。。等会儿人家又变成人了可咋办?)

问题8,问题9,问题10.。。

。。。。。。。。。以上废话请自动略过。。。。。。。。。。。

在应对诸多变化的情况下,我们似乎没有办法都实现,即便都实现了,感觉也和传统的面向过程没什么区别,一个无敌巨大的GAME类。面向对象就是如此了吗(废话,当然不是。)?原因很简单,这是因为我们停留在仅使用“封装”(类)的层面上,还没有使用所谓的“面向对象”。

表面上看,这个程序是由“游戏”和“人”两个简单的对象组成,但,什么是人?

image

简单的来说,为了区分所有的source,我们必须有个标识(HashId)。人作为一个独特的对象,拥有左右手及记忆对话(Conversation)的能力。

我们再来分析一下,什么是游戏?

广泛意义上来说,游戏即为对游戏对象根据游戏规则进行重规划的活动。游戏更像一个Manager,来依据一定的规则管理Source。这样,游戏就变成了一个SourceManager。那么这个Manager有些什么功能呢?依据这个题目,Manager必须要有“添加”,“删除”Source的能力才能进行所谓的“Management”(管理)。

我们便有了如下这个类:

image

为了提高代码复用与规范代码结构,我们把BaseSourceManager,创建为抽象类,T用来表示参与游戏的物体的类型,Methods均为抽象方法。针对此题目,我们是对人进行操作,我们便继承这个Base,实现一个对人进行操作的SourceManager:

image 

RefreshPeopleConnection这个方法是用来更新人的左右手链接的(当然,我们可以在添加删除的方法中更新,偷懒就这么做了,而且感觉不会出问题,逻辑也统一一点 哈哈)。

至此,我们已经有一个简单的SourceManager了,但是游戏的逻辑啊规则啊,开始结束啊完全都没加进去,该怎么处理呢?

image

 

我们在此引入Decorator(装饰模式)的设计模式,为什么要使用它而不是别的呢?简单的说,我认为游戏规则只是对游戏的一种装饰,我们可以一次使用一个规则,也可以使用好几个规则,这都不应该让我们对现有规则产生任何影响,使用装饰模式会极大的精简各种功能组合子类的数量,也就提高了代码复用度及可维护性。

这个类按照经典的装饰模式编写:

   1: namespace OO_Program
   2: {
   3:     public abstract class DecoratorSourceManager : BaseSourceManager<IPerson>
   4:     {
   5:         private BaseSourceManager<IPerson> sourceManager;
   6:  
   7:         public DecoratorSourceManager(BaseSourceManager<IPerson> sourceManager)
   8:         {
   9:             this.sourceManager = sourceManager;
  10:         }
  11:  
  12:         public override void Add(IPerson source)
  13:         {
  14:             this.sourceManager.Add(source);
  15:         }
  16:  
  17:         public override void Remove(IPerson source)
  18:         {
  19:             this.sourceManager.Remove(source);
  20:         }
  21:  
  22:         public override IList<IPerson> GetAllSource()
  23:         {
  24:             return this.sourceManager.GetAllSource();
  25:         }
  26:     }
  27: }

 

感觉是一点改动也没有吧,完全和教科书上的一致。

好了,终于到游戏规则出场的时候了:

image

把这个游戏作为SourceManager的一种装饰类的形式出现,并添加了自己的方法(Play)和事件(GameOver, GameStarted).

至此,整个程序主体架构已经搭建完毕了,我们该如何使用呢?

   1: var game = new PersonNumberOffGame(new PNOGameSourceManager());
   2: //初始化游戏参与者
   3: for (int i = 0; i < 17; i++)
   4: {
   5:     Person p = new Person();
   6:     game.Add(p);
   7: }
   8:  
   9: game.GameStarted += new EventHandler<GameEventArgs>(game_GameStarted);
  10: game.GameOver += new EventHandler<OO_Program.EventHandlers.GameEventArgs>(game_GameOver);
  11: game.Play(0);

 

自己感觉还是比较有语义的,逻辑也很流畅,没有什么废代码。一行一行解释一下:

1 创建一个用“PNOGameSourceManager”管理Source的,依据“PersonNumberOffGame”为规则的game对象。

3-7 创建17个person,加入游戏中来(注意,此Add方法实际为“PNOGameSourceManager”的方法。这就是装饰模式的好处啊~~~)

9 如果游戏开始,执行“game_GameStarted”

10 如果游戏结束,执行“game_GameOver”

11从第一个元素开始玩游戏。

看一下结果处理的代码:

   1: static void game_GameOver(object sender, OO_Program.EventHandlers.GameEventArgs e)
   2: {
   3:     if (e.State == GameStates.SuccessfulEnd)
   4:     {
   5:         var game = sender as PersonNumberOffGame;
   6:         //Console.WriteLine("最后剩下的人为:[{0}]", game.GetResult().ResultSource.HashId);
   7:         game.GetResult().Print();
   8:         Console.WriteLine("游戏成功结束");
   9:     }
  10: }

 

这个更简单了:

3 如果游戏成功结束

5 不得不写的废话

7 获取游戏结果并打印。

8 打印一句废话 :)。

至此,程序架构完毕。(耐心看至此的童鞋请在下面报到~~~~ 每人赠送回复一条~~)

我们费了这么多神,把程序搞得这么复杂(其实我认为是更清晰了),获得了什么好处了吗?

场景一:这个程序只能获得最后剩下的那个人,我们需要知道每一步退出的人员的列表。

合情合理的需求,直接上图:

image

创建一个名为“LogableSourceManager”的装饰类,在Remove方法中加入Log输出:

   1: public class LogableSourceManager : DecoratorSourceManager, ILogable
   2:     {
   3:         public List<PNOGameLog> Logs
   4:         {
   5:             get;
   6:             set;
   7:         }
   8:  
   9:         public LogableSourceManager(BaseSourceManager<IPerson> sourceManager)
  10:             : base(sourceManager)
  11:         {
  12:             this.Logs = new List<PNOGameLog>();
  13:         }
  14:  
  15:         public override void Remove(IPerson source)
  16:         {
  17:             base.Remove(source);
  18:             var log = new PNOGameLog(source, "移除出队列");
  19:             this.Logs.Add(log);
  20:             Console.WriteLine("[{0}]说了{1},被{2}",
  21:                     log.EventSource.HashId,
  22:                     log.EventSource.Conversation,
  23:                     log.Message);
  24:         }

 

重点为15-23行。

我们并不需要重新实现一边remove只需加入我们需要增添的功能。

使用方法为:

   1: static void Main(string[] args)
   2: {
   3:     var game = new PersonNumberOffGame(new LogableSourceManager(new PNOGameSourceManager()));
   4:     //var game = new PersonNumberOffGame(new PNOGameSourceManager());
   5:  
   6:     //初始化游戏参与者
   7:     for (int i = 0; i < 17; i++)
   8:     {
   9:         Person p = new Person();
  10:         game.Add(p);
  11:     }
  12:  
  13:     game.GameStarted += new EventHandler<GameEventArgs>(game_GameStarted);
  14:     game.GameOver += new EventHandler<OO_Program.EventHandlers.GameEventArgs>(game_GameOver);
  15:     game.Play(0);
  16: }

 

只许修改一行调用代码,便加入了Log输出功能。原有程序结构一个字都不用改。是不是很爽。哈哈哈哈。。(咳,咳。。岔气。。)

来个整体架构图:

image

P.S.谁知道怎么用live writer直接上附件?
代码全文如下:
/Files/pandora/OO_Program.zip

posted on 2009-09-05 07:20  Pandora  阅读(2331)  评论(22编辑  收藏  举报

导航