C# 继承、接口与多态

来源:互联网 发布:itunes怎么安装软件 编辑:程序博客网 时间:2024/04/30 05:07

C# 继承、接口与多态

  我在这里想谈一谈在C#中的继承(继承于基类或接口)和多态(如方法的覆写)。继承和多态是面向对象编程中两个重要的特性,这种特性和编程语言本身是没多大关系的,因此我先会用非编程的思维来谈一谈我对它们的认识,然后再谈一谈它们在C#中的实现方法。

  1、继承的含义

  所谓继承,就是“站在巨人的肩膀上”进行扩展。例如,最开始的铅笔尾端是没有橡皮擦的,后来有个男孩在尾端装上了橡皮擦,铅笔的销量一路飙升,他也成了百万富翁,这说明好的灵感多么的重要!(偏题了啊喂!感兴趣的童鞋请戳:http://baike.baidu.com/view/1628703.htm)好吧,回归正题,这就是一个典型的继承。我们在做一个“橡皮头铅笔”时,不需要想如何做一支铅笔,而是拿来一支现成的铅笔,在它上面装上橡皮而已。继承可以帮助我们重用组件,继承是人类不断进步的源泉(貌似又偏题了!)。

  2、多态的含义

  从非编程的角度来看“多态”,可以理解成“不同的人执行相同的条例,却有不同的行为”。在编程的角度来看,是“不同的对象执行相同的方法,却有不同的行为”。怎么来理解呢,我可以打以下这个比方。

  某学校的学生规范中有写道:“老师进教室时,学生坐好如下课前准备:安静坐好。此时值日生应该上讲台擦黑板,课代表收本科目作业。”教室里,除了老师外,其他的人都是学生(包括课代表和值日生),在执行这些行为的时候,课代表和值日生为什么不和规范中所提到的“学生”一样安静坐好呢?因为规范中还说到,他们有自己的职责:一个应当擦黑板,一个应当收作业。这就是多态,虽然学生们都要做好课前准备,但是却不全部相同。由“学生”派生出来的“课代表”要收作业,由“学生”派生出来的值日生要擦黑板,而“学生”这个“基类”的各个同学只需要安静坐好。他们都执行着相同的条例(做课前准备),却有不同的行为。

  3、接口的含义

  通俗地来打比方,我要开发一个多功能插线板,其中的插头孔要支持大陆标准、香港标准、英国标准和美国标准。我只需要把插线板按照标准挖出这4个孔就可以了,我就可以说,我这个插线板可以插大陆、香港、美国、英国的电源。至于插上去后怎么样,我不关心,我只关心,它可以插这些电源。再例如,我开发了一个新的电子产品,此产品要支持用USB来传数据,我就要预留一个USB接口,至于接上USB数据线之后怎么样,这个是以后的事情,总之它先要支持USB接口。

  从程序的角度通俗的来理解接口,就是定义程序有这样的一种“兼容性”,而不去管接口内部是怎么实现的。因此,作为接口,它只包含定义,而不包含具体的实现,这也是它和抽象类的最大区别(抽象类中可以包含方法的实现)。

  4、C#中的继承与多态

  C#不像C++那样区分“共有继承"、"私有继承"。所有的继承均是共有的,也就是基类的所有成员的访问性在派生类中都不会变。并且,C#中只支持单一继承,但是可以继承多个接口。这个很容易理解,一辆高级汽车,它只继承于一个对象(一辆普通汽车),而不会同时继承于一辆汽车和一架飞机;但是它可以用很多个接口,如加油的接口和充电的接口。Java和C#的设计值发现多重继承在使用起来太蛋疼了,因此在他们的语言中去掉了这个特性。

  类的访问性、成员定义方法我就不进行阐述了,因为这是很基本的东西。下面我主要来谈一谈抽象类、虚函数,它们是实现多态的重要部分。

  有如下代码,定义了一个Creature类,Creature类派生了两个子类:Animal和Human。Creature类有一个抽象方法Eat。抽象方法是指,继承于本抽象类的派生类都必须自己实现这个方法。抽象方法只是一个声明,没有方法体,包含抽象方法的类一定要为抽象类。由于Eat是抽象方法,Human和Animal继承于抽象类Creature,因此它们都必须要实现自己的Eat方法。代码如下:

using System;namespace Demo{    public abstract class Creature    {        public abstract void Eat();    }    public class Human : Creature    {        public override void Eat()        {            Console.WriteLine("用火先将食物烤熟再吃。");        }    }    public class Animal : Creature    {        public override void Eat()        {            Console.WriteLine("直接吞下肚子。");        }    }    public class Program    {        static void Main(string[] args)        {            Creature h = new Human();            Creature a = new Animal();            h.Eat();            a.Eat();        }    }}

  运行后,屏幕输出如下:

  用火先将食物烤熟再吃。
  直接吞下肚子。

  显然,前者是执行h.Eat()的结果,后者是执行a.Eat()的结果。派生于同一个类的两个派生类,其Eat方法经过了重写表现出了不同的行为。需要注意的是,在覆写基类的抽象方法时,必须要使用override关键字,否则无法通过编译。

  抽象类Creature是无法实例化的(无法用new Creature()来实例化),因为它还有一个方法Eat没有实现。如果我们想让Creature有个“默认”的Eat方法,应该怎么做呢?这个时候就需要利用虚函数了。

  代码如下:

using System;namespace Demo{    public class Creature    {        public virtual void Eat()        {            Console.WriteLine("生物消化了食物。");        }    }    public class Human : Creature    {        public override void Eat()        {            Console.WriteLine("用火先将食物烤熟再吃。");        }    }    public class Animal : Creature    {        public override void Eat()        {            Console.WriteLine("直接吞下肚子。");        }    }    public class Program    {        static void Main(string[] args)        {            Creature h = new Human();            Creature a = new Animal();            Creature c = new Creature();            h.Eat();            a.Eat();            c.Eat();        }    }}
  上例中,我们为Creature类添加了一个虚方法Eat。也就是说,如果Creature的派生类没有被覆写,它就调用基类的Eat。所谓被覆写,就是派生类中存在同名函数,且使用了关键字override。在之前提过的学生、课代表、值日生的例子中,代码如下所示:

using System;namespace Demo{    public class Student    {        public virtual void ClassBegin()        {            Console.WriteLine("安静坐好。");        }    }    public class StudentOnDuty : Student    {        public override void ClassBegin()        {            Console.WriteLine("擦黑板。");        }    }    public class Representative : Student    {        public override void ClassBegin()        {            Console.WriteLine("收作业。");        }    }    public class Program    {        static void Main(string[] args)        {            //定义学生            Student s = new Student();            //定义课代表            Student r = new Representative();            //定义值日生            Student d = new StudentOnDuty();            s.ClassBegin();            r.ClassBegin();            d.ClassBegin();        }    }}

  你可能会注意到,只有我们用基类的类型来定义一个派生类的对象时,才有多态的机制。多态有什么意义呢?举例来说,假设有一个方法,需要传入一个Student类,它可以调用Student类中的ClassBegin。如果没有多态,我们就要这样做:

public void WhenClassBegin(Student s){    s.ClassBegin();}public void WhenClassBegin(Representative s){    s.ClassBegin();}public void WhenClassBegin(StudentOnDuty s){    s.ClassBegin();}

  如果再增加一个派生类,我们就要为WhenClassBegin多增加一个版本,这样做工作量巨大而且不易于维护!现在有了多态,有了虚函数,我们只需要这样来写:

public void WhenClassBegin(Student s){    s.ClassBegin();}

  因为Student对象能知道,它究竟要调用哪个ClassBegin方法。


  5、C#中的接口

   C#中定义接口十分简单。接口中的方法不包含可访问性修饰符,也不包含方法体。如以下就是一个接口的声明和使用:

using System;namespace Demo{    public interface IEdible    {        void Eat();    }    public class Program : IEdible    {        public static void Main(string[] args)        {            IEdible food = new Program();            food.Eat();        }        public void Eat()        {            Console.WriteLine("Yummy!");        }    }}

  需要注意的是IEdible food = new Program()这句话的意思是,实例化一个继承了IEdible的对象。一定要注意,接口是不能实例化的,只能实例化一个具有某种接口的对象。如果Program没有继承IEdible,那么编译器就会报错。

  这个就是我对继承、接口、多态的一些理解,这些概念广泛地运用在各种面向对象编程中,希望对大家有用。

0 0
原创粉丝点击