关于某道C#上机题的OO

前两天在园子里,有人出了一道《关于一道C#上机题的一点想法》,大概的意思呢是利用OO的思想来进行编程,接着又有一位朋友,也写了自己的答案,此朋友非常厉害,从类图,接口,封装,多态,都一一实现,实在让我佩服,不过真有点过度设计的味道,接着又有一大虾,完成了自己的OO答案,把泛型,可变,不可变都一一列举,实在令人佩服啊,可我觉得,或许是我理解错了,但我觉得三位,你们都偏离了题目,偏离了OO,你们只是利用了OO的特性。

题目

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

点评

我不是高手,没什么资格点评大家,只是提出自己的看法。

Joyaspx 只实现了一个对象,那就是人,但是却把“到3退出”给放在执行方法中,而人这个对象,还要知道他的哥哥弟弟,或许是Joyaspx上机时间不够,感觉这个方式不是面向对象的进行开发,还是用了面向问题来解决了。

OOLi 不得不佩服,OO的一切,从设计到接口到实现都一一实现,实在是过度设计了,但其中的OO实在不敢恭维,比如初始数据时,使用了硬编码,第一个人还需要给他一个编号,还给Person这个对象配备了一个State,根据State来判断是否该移除,他的退出也很有趣,把自己割掉。。。。告诉哥哥,你没有我这个弟弟,你的弟弟是我的小弟弟,那我想问下,我去哪里了?

YangQ 这位仁兄,我不得不说下,你的程序真的不是面向对象,是完全的面向过程来开发,虽然你用到了泛型,但不是说用了泛型就是面向对象开发了,希望兄台能继续努力,掌握和了解一下什么是面向对象开发。

我的理解

题目很短,我们也应该很好理解他,一共只有一个对象,那就是人Person,这是没有错误的,大家都想到的。但在这到题目中,并没有说我需要知道下一个人是谁,上一个人是谁,因为他们都是在玩游戏,一个报数的游戏,“到3退出”只是游戏的一个规则,不是每一个人都需要玩这个游戏,我们只需要17个人而已,所以对Person对象而言,并不需要那么复杂的Perv,Next,包括退出的动作,也不属于“人”的范畴,只是“人”在“报数游戏”的场景中,对于OO编程来说,一切皆对象,也就是说,游戏也是对象,呵呵。

此题是非常微妙的,如果没有要求OO的话,它应该是一个数据结构的算法问题,也就是前几位大哥说的那种,是什么结构我叫不出来,我自己认为是一个环状的,大家手拉手拉成圈的。

开始

理解了题目,我们知道需要2个对象,Person,Game,游戏必须依赖于人,因为没有人,游戏也不会开始,人不需要知道游戏,只要参加的人了解游戏就可以。我们看下Person对象的定义:

public class Person
{
      public Person(int personID)
{
      this.PersonID = personID;
}
      public int PersonID { get; set; }
      public void Say()
      {
            if (this.Said != null) this.Said(this, new PersonEventArgs(this));
      }
      public event EventHandler<PersonEventArgs> Said;
}

每一个人都有自己的ID,因为是演示,姓名之类的,我就不加入了。有一个Say的方法,因为我们报数需要嘴巴来说,其中呢也不执行什么内容,如果需要内容,我们可以自己添加。对于人来说,我们每次说话不一定需要每次自己或者别人来做出响应,但我需要通知某一个对象,我说话了,就算你是对墙说话,你还是通知了墙,“Hi,墙,我说话了”,所以我加入了Said一个委托事件,目的是把我说话了通知给某个对象,在这个题目中,我通知给“游戏”这个对象,这应该属于通知模式了吧,呵呵。

PersonEventArgs:

public class PersonEventArgs : EventArgs
{
      public PersonEventArgs(Person person)
      {
            this.Person = person;
      }
      public Person Person { get; set; }
}

接下来重点说说游戏,对于我们其他人来说(除了游戏中人),我是裁判,我只需要说游戏开始,就可以了,达到某个条件的时候,Game Over。所以我们只需要发命令,让游戏开始就好了。

Game game = new Game(17);       //17 代表参加的人数
game.Start();

这是程序测试的接口了,那我们构造这个Game对象就相对简单了,因为只要告诉它,多少人参加,然后游戏开始就OK了,我们只需要公开一个构造函数,一个开始方法就好了。

public class Game
{
      public Game(int personNumber)
      {
      }
      public void Start()
      {
      }
}

这样我们完成了封装,呵呵,对于外部,我们只需要知道这些已经足够了,那接下来,我们看看Game中,我们还需要些什么。

既然我们需要人,而且是很多人玩游戏,那一定有一个Players的属性,游戏开始呢,需要开始报数,这时候我们需要一个一个人去进行报数,报数的结果呢,是游戏的一个状态(注意,是对象的状态,不是类型的),我们看下我写的Game类:

public class Game
{
      private int CurrentNumber = 0;
      private List<Person> CurrentQuitPersons = new List<Person>();
      private List<Person> Players { get; set; }
      private event EventHandler<PersonEventArgs> GameOver;
      public Game(int personNumber)
      {
            Ready(personNumber);
      }
      public void Start()
      {
            ++CurrentNumber;
            this.GameOver += new EventHandler<PersonEventArgs>(Game_GameOver);
            Go();
      }
      private void Ready(int personNumber)
      {
            this.Players = new List<Person>(personNumber);
            for (int i = 0; i < personNumber; i++)
            {
                  Person person = new Person(i);
                  person.Said += new EventHandler<PersonEventArgs>(Person_Said);
                  this.Players.Add(person);
            }
      }
      private void Go()
      {
            var persons = this.Players;
            persons.ForEach(p =>
            {
                  p.Say();
                  CurrentNumber++;
            });
            if (this.Players.Count > 1)
            {
                  if (CurrentQuitPersons.Any())
                  {
                        this.Players.RemoveAll(p => CurrentQuitPersons.Contains(p));
                        CurrentQuitPersons.Clear();
                  }
                  Go();
            }
            else
            {
                  this.GameOver(this, new PersonEventArgs(this.Players.First()));
            }
      }
      private void Person_Said(object sender, PersonEventArgs e)
      {
            if (CurrentNumber % 3 == 0)
            {
                  CurrentQuitPersons.Add(e.Person);
                  Console.WriteLine("The player quit, ID : {0}, CurrentNumber:{1}", e.Person.PersonID, CurrentNumber);
            }
      }
      private void Game_GameOver(object sender, PersonEventArgs e)
      {
            Console.WriteLine("Last Person's Person ID is {0}", e.Person.PersonID);
            Console.WriteLine("Game Over.");
      }
}

呵呵,不好意思,比较长,请大家耐心看完。

其中呢有一个CurrentNumber字段,代表着这个Game对象的一个当前状态,也就是报数的一个数字。Players呢,是参加的人员,在构造函数的时候,会去准备一下,也就是初始化这个Players属性,每一个人呢,我们会分配一个ID,然后会委托一个Person_Said的委托,目的是让Game知道,Play报数了,然后根据这个数多少来反应一个动作。这个题目中呢,也就是“到3退出”。

一切都准备好了之后,我们就开始Start了,刚开始,从1开始,当前数字转变为1(为了区分结果,我把人的初始序号,是从0开始的),每个人开始报数,在Go这个方法中呢,会判断一下,如果还剩下一个人的时候,游戏结束,好,我们看下运行结果吧。

image

ok,程序结束,运行正确,也是我们预料的。

总结

这次呢,正好有时间,有机会让自己体验一下面向对象的编程,其实题目并不是很难,要看大家的理解是如何的,不是说用了面向对象的特性就是面向对象的一个开发,这完全是一个误区,就好象你在项目中,用了一个接一个的模式一样,模式狂人并不代表你的程序是一个模式的程序,模式是在开发以后逐渐形成,能让我们更好的进行扩展、封装等,让每个人能更好的理解(比如UML),所以面向对象也是一样,它的特性完全是因为在开发过程中,人们发觉了这些特性,把它列举出来,并形成了一个规范文档,让大家能快速的上手了解面向对象,并不是说有了这些特性,就是面向对象开发。再通俗一点,歌手的特性会唱歌,但不是会唱歌的人就是歌手一样。

不足

我不能说我的解答非常完美,只是借此机会阐述自己的一些看法和观点。不足之处也有,因为我完全没有考虑算法,完全没有考虑性能。除此之外,其中也有一个败笔,那就是CurrentQuitPersons这个字段,原先我想是在Person_Said的时候,到3直接退出Players的,但发觉Remove后,序号会直接重新排列,造成了误差,所以利用这个字段,我在每一轮结束的时候,Remove这一轮需要去除的玩家,这样保证了报数的连续性,实在大为不爽,不知道大家有什么好的方法来解决呢?

posted @ 2009-08-30 21:14  James.Ying  阅读(5531)  评论(35编辑  收藏  举报