关于某道C#上机题的OO

简介: 前两天在园子里,有人出了一道《关于一道C#上机题的一点想法》,大概的意思呢是利用OO的思想来进行编程,接着又有一位朋友,也写了自己的答案,此朋友非常厉害,从类图,接口,封装,多态,都一一实现,实在让我佩服,不过真有点过度设计的味道,接着又有一大虾,完成了自己的OO答案,把泛型,可变,不可变都一一列举,实在令人佩服啊,可我觉得,或许是我理解错了,但我觉得三位,你们都偏离了题目,偏离了OO,你们只是利用了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这一轮需要去除的玩家,这样保证了报数的连续性,实在大为不爽,不知道大家有什么好的方法来解决呢?

相关文章
|
算法 C# 数据库
【干货】一份10万字免费的C#/.NET/.NET Core面试宝典
C#/.NET/.NET Core相关技术常见面试题汇总,不仅仅为了面试而学习,更多的是查漏补缺、扩充知识面和大家共同学习进步。该知识库主要由自己平时学习实践总结、网上优秀文章资料收集(这一部分会标注来源)和社区小伙伴提供三部分组成。该份基础面试宝典完全免费,发布两年来收获了广大.NET小伙伴的好评,我会持续更新和改进,欢迎关注我的公众号【追逐时光者】第一时间获取最新更新的面试题内容。
494 1
|
存储 Linux 调度
io复用之epoll核心源码剖析
epoll底层实现中有两个关键的数据结构,一个是eventpoll另一个是epitem,其中eventpoll中有两个成员变量分别是rbr和rdlist,前者指向一颗红黑树的根,后者指向双向链表的头。而epitem则是红黑树节点和双向链表节点的综合体,也就是说epitem即可作为树的节点,又可以作为链表的节点,并且epitem中包含着用户注册的事件。当用户调用epoll_create()时,会创建eventpoll对象(包含一个红黑树和一个双链表);
250 0
io复用之epoll核心源码剖析
|
开发框架 Java 中间件
.NET/.NET Core相关面试题
.NET/.NET Core相关面试题
276 0
|
SQL 搜索推荐 算法
一份阅读量突破10万+的C#/.NET/.NET Core面试宝典(基础版)
一份阅读量突破10万+的C#/.NET/.NET Core面试宝典(基础版)
194 0
|
算法 开发工具 git
时间紧任务急,如何在LeetCode刷题
很多公司都会面试算法题,然而很多小伙伴平时工作很忙,没有时间或没有养成刷题的习惯,面试准备周期时间也很紧张,没办法刷完LeetCode,往往慌慌张张刷了一些题,然而其实效果也不好。 当然这里还是建议大家平时多看看算法题,毕竟程序=数据结构+算法,对你以后的编程工作来说是大有好处的。
时间紧任务急,如何在LeetCode刷题
C# .net webapi使用swagger时显示controller注释
C# .net webapi使用swagger时显示controller注释
529 0
|
存储 测试技术 数据库
Entity Framework Core Migrations 超厉害!轻松实现数据库版本控制,让你的开发更顺畅!
【8月更文挑战第31天】数据库的演变是软件开发中不可或缺的部分。随着应用发展,数据库需不断调整以适应新功能。Entity Framework Core Migrations 作为数据库的守护者,提供强大的版本控制手段,确保不同环境下的数据库一致性。通过创建和管理迁移脚本,开发者可以有序地管理数据库变更,避免混乱和数据丢失。安装并配置好 EF Core 后,可以通过命令行工具轻松创建、应用及回滚迁移,如 `dotnet ef migrations add InitialMigration` 和 `dotnet ef database update`。
238 0
|
数据库
优化数据加载策略:深入探讨Entity Framework Core中的懒加载与显式加载技术及其适用场景
【8月更文挑战第31天】在 Entity Framework Core(EF Core)中,数据加载策略直接影响应用性能。本文将介绍懒加载(Lazy Loading)和显式加载(Eager Loading)的概念及适用场景。懒加载在访问导航属性时才加载关联实体,可优化性能,但可能引发多次数据库查询;显式加载则一次性加载所有关联实体,减少查询次数但增加单次查询的数据量。了解这些策略有助于开发高性能应用。
216 0
|
缓存 监控 安全
API网关的用途
【8月更文挑战第23天】
537 0
|
缓存
IEC61850 调试工具 工程师必备
IEC 61850 是国际电工委员会(IEC)制定的一项国际标准,主要用于电力系统自动化领域,特别是变电站自动化系统。IEC 61850 是电力系统自动化领域的一项重要标准,通过标准化的通信协议和数据模型,显著提升了变电站及其相关系统的互操作性和灵活性。
283 0
IEC61850 调试工具 工程师必备

热门文章

最新文章