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,那么编译器就会报错。
这个就是我对继承、接口、多态的一些理解,这些概念广泛地运用在各种面向对象编程中,希望对大家有用。