面向对象主要概念

【回目录】

说实话,面向对象是个很大很广的概念,我可不敢在园子里瞎白活,以免“误入牛群深处,引来砖头无数”。但是作为面试常考的题,又不得不列举出来,在此,我主要是和大家一起回顾一下面向对象中的几个核心概念,温故罢了,绝无它意。

说到面向对象这个概念很大很广,其实我觉得也不必被这个“很大很广”吓着了,学习面向对象思想,切不可将其专门孤立为一门学科来学习,其实面向对象就在我们日常生活当中,随时随处都能见到,而不仅仅是软件开发。软件开发不过是把我们人类看待世界的思维方式总结抽象出来用到程序设计上,起了个名字叫“面向对象”,可千万别被专业术语给镇住了,IT人士有这毛病,喜欢故弄玄虚,其实例子太常见了,你家马桶就是面向对象的。

面向对象有三个要素:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。把英文也写出来不是卖弄而是给大家伙儿提个醒,很多时候不错的公司面试往往会用英文,可千万别在词汇量上栽跟头,太不划算了。好,废话少说,接下来我们就来挨个儿探讨以上三个概念吧。

什么是封装?面向对象思想有个忌讳,那就是把对象自己的属性和内部实现细节暴露给别的对象,虽是代码但也应该享有隐私权。把不想或者不该告诉别人的东西封起来,把可以告诉别人的公开,这就是封装的基本概念。那什么是该封装起来什么是该公开的呢?很简单,我们来看看下面这段代码:

   1: public class Person
   2: {
   3:     public string Name { get; private set; }
   4:     protected int age = 0;
   5:  
   6:     internal void SetName(string name)
   7:     {
   8:         if (String.IsNullOrEmpty(name))
   9:         {
  10:             return;
  11:         }
  12:  
  13:         this.Name = name;
  14:     }
  15: }

我们声明了一个Person类,里面有一个Name属性。注意,Name属性的访问器(get)是公开的,但设置器(set)是私有的,试想一个人如果走在大街上谁见了他都可以给他取个名字,那岂不乱了套了。但人又不能没有名字,所以有一个SetName的方法,前面说了名字不是谁都能取的,因此我们给这个方法加了一个internal访问修饰符,也就是说当前程序集里可以调用,我们把“当前程序集”设想成这个人所在的家庭,一个小孩子生下来,可能是爷爷奶奶给取名,也可能是爸爸妈妈取名。另外,还有个protected的age属性,女孩子的年龄往往是比较保密的,一个女的听到人家说:“啊,你都35了呀!”肯定不会开心的,所以保护起来,小心别让人知道为好。不论用什么访问修饰符,封装的目的就是根据类之间的关系与设计需要,把不该暴露出来包裹好,把可以让别的对象调用的公开,让对象在内存里活得井然有序。如果你有点儿拿捏不准什么是该暴露出来的什么是该封装起来的,那么你就联系到实际生活中的例子去想想。看看你家的马桶,放水的按钮是暴露出来的,但您按下按钮之后内部是如何运作把水放出来的,这就不用用户关心了,自然也就封装起来了,如果您还印象不够深,那就想想,马桶排水的管道应该暴露还是封装起来呢。

也许有朋友会问,既然女孩子的年龄是隐私,那么为什么不干脆用private呢,这样岂不是封装得更彻底?嗯,只要private了,除了自己,谁都甭想知道。这比较适合犯罪记录,比如“private int _killedPeopleAmount;”我们之所以把age属性设为protected的,就是考虑到面向对象的另一个概念:继承。

所谓“龙生龙,凤生凤,老鼠的儿子会打洞”。同类的事物,可以把公共的部分抽象出来放在基类中,子类去继承,就不用在每个子类中都单独声明了。如果把age设置为private,子类将不能继承,不符合面向对象的思想。请看下面这个类:

   1: public class Chinese : Person
   2: {
   3:     public object HuKou { get; set; }
   4: }

我们把上面的Person类当作人类的基类,其中的Name和age以及SetName方法都是人类共同的地方,因此声明Chinese的时候只需要继承Person类就可以获得这些属性和方法,没必要自己再去重新声明一遍。精力用在刀刃上,考虑Chinese类的属性和方法的时候只需要考虑那些具有中国特色的就行了,例如户口。

接下来我们来探讨探讨什么是多态。所谓多态,中国有句古话可以概括:龙生九子,种种不同。都是龙的儿子,又各有差别,不过这样说起来有点儿泛泛,只是辅助记忆而已,可千万别在这上面认死理儿。多态有两种表现方式,一是同一个类中有多个名字一样但签名不同的方法;二是子类改写父类中方法的内部实现。其实“龙生九子,种种不同”这个概括只能映射其中第二点表现方式,也就是virtual和override之间的关系。至于第一点嘛,可以追溯到多态的英文单词的起源,据说这个词来源于西方神话故事里一个叫Polymorph神,他具体从事什么工作不重要,关键是他有个特点,白天一个样晚上一个样,同一个人表现出不同的样子,人格分裂的典型临床症状,软件开发前辈将他抽象成面向对象中的“多态”,可以很好地解释方法重载。

首先我们来看看方法重载:

   1: public class UserManager
   2: {
   3:     public object GetUser(int id)
   4:     {
   5:         // TODO: Find this user instance by id.
   6:         return new object();
   7:     }
   8:  
   9:     public object GetUser(string name)
  10:     {
  11:         // TODO: Find this user instance by name.
  12:         return new object();
  13:     }
  14: }

我们假设有UserManager这样一个工具类,提供了获取用户实例的机制,但又有两种方式,一是通过id另一种则是通过name,通过方法重载,我们只需要变动一下方法的入参数据类型从而改变了函数签名即可,没必要分别声明GetUserByID和GetUserByName两个方法,那样的话会让整个类在被调用时显得很臃肿,要不然像MessageBox.Show这样提供21种重载方式的方法,对于开发者和设计者来说岂不都是噩梦?

根据我的经验,面向对象在面试中主要考察的部分就是多态,而在多态中主要考察的就是virtual和override之间的关系。我们来看码说话吧:

   1: public class Dragon
   2: {
   3:     public virtual void Fly()
   4:     {
   5:         Console.WriteLine("I'm flying.");
   6:     }
   7: }
   8:  
   9: public class DragonSonA : Dragon
  10: {
  11:     public override void Fly()
  12:     {
  13:         base.Fly();
  14:     }
  15: }
  16:  
  17: public class DragonSonB : Dragon
  18: {
  19:     public override void Fly()
  20:     {
  21:         Console.WriteLine("I can't fly.");
  22:     }
  23: }

这段示例代码比较浅显易懂。我们声明了一个Dragon类,它有一个飞行的方法,因此它可以在天上随意地飞,它的儿子也会从他那里继承到这个飞翔的方法,但又“种种不同”,有的龙恐怕并不会飞,例如DragonSonB。甭管龙的儿子是不是都会飞吧,这段代码主要是展示一下,在父类中有的方法虽然会被子类继承,但将来在子类使用时可能有不同的实现机制,这时候就需要在父类中声明该方法时加上virtual关键字,以表示将来是可以改写的,在子类中需要则使用override关键字,表示儿子自有主见。

很多时候面试官会围绕virtual和override问一些其它情况,例如如果父类里面的方法没有virtual关键字,光是子类里方法前有override关键字呢?这种情况不用多想,语法错误,就算面试官答应,编译器也不会答应的。需要注意的是,如果子类里继承的方法如果没有override关键字,会是怎样呢?当我们把DragonSonB.Fly方法前的override关键字去掉,这种情况编译器在编译的时候会默认在函数声明前面加上一个new关键字,这就阻断了继承链,也就是说DragonSonB.Fly和Dragon.Fly没有什么关系了,只不过方法名雷同而已。具体怎么体现呢?我们来试试下面的代码:

   1: static void Main(string[] args)
   2: {
   3:     Dragon dragon = new DragonSonB();
   4:     dragon.Fly();
   5:  
   6:     Console.ReadLine();
   7: }

调用dragon.Fly方法时,编译器会顺着继承链往下找,一直找到合适的那个方法体。如果DragonSonB.Fly方法有override关键字,则说明它是从Dragon那儿继承来的,因此dragon.Fly实际上调用的是DragonSonB.Fly,输出结果就是“I can’t fly.”。但如果DragonSonB.Fly去掉了override关键字或者换成了new关键字,那么就意味着这个Fly方法不再是从Dragon那儿继承来的了,因此在调用dragon.Fly方法时,继承链就不能到达DragonSonB.Fly方法而只能停留在它的上一级,也就是调用Dragon.Fly方法。

搞清楚了上面提到的概念,估计面试中面向对象尤其是多态部分问题就不大了,还请各位高手不吝赐教,多多补充完善!

Everything is object.

posted @ 2008-08-19 17:40  麦机长  阅读(14644)  评论(30编辑  收藏  举报