来自http://blog.csdn.net/softart/archive/2007/10/27/1846041.aspx程序员
多态(Polymorphism)是面向对象(Object-Oriented,OO)思想"三大特征"之一,其他两个分别是封装(Encapsulation)和继承(Inheritance)--可见多态的重要性。或者说,不懂得什么是多态就不能说懂得面向对象。面试
多态是一种机制、一种能力,而非某个关键字。它在类的继承中得以实现,在类的方法调用中得以体现。编程
先让咱们看看MSDN里给出的定义:ide
Through inheritance, a class can be used as more than one type; it can be used as its own type, any base types, or any interface type if it implements interfaces. This is called polymorphism. In C#, every type is polymorphic. Types can be used as their own type or as a Object instance, because any type automatically treats Object as a base type.函数
译文:经过继承,一个类能够被看成不止一个数据类型(type)使用,它能够被用作自身表明的数据类型(这是最经常使用的),还能够被看成它的任意基类所表明的数据类型,乃至任意接口类型--前提是这个类实现了这个接口。这一机制称为"多态"。在C#中,全部的数据类型都是多态的。任意一个数据类型均可以被看成自身来使用,也能够看成Object类型来使用(我怀疑原文有问题,那个instance多是原做者的笔误),由于任何数据类型都自动以Object为本身的基类。学习
呵呵,除非你已经早就知道了什么是多态而后翻过头来看上面一段话,否则我敢打保票--我是清清楚楚的,你是稀里糊涂的。OK,不难为你们了,我用几个句子说明一下多态的思想。ui
咱们先把前文中提到的"接口"理解为"一组功能的集合",把"类"理解为功能的实现体。这样的例子多了去了。咱们就拿生物界作比喻了:spa
功能集合1:呼吸系统.net
功能集合2:血液循环系统orm
功能集合3:神经系统
功能集合4:语言系统
类1:灵长类动物。此类实现了1到3功能集合。
类2:猴子类。继承自类1。新添加了"爬树"的功能。
类3:人类。继承自类1。同时实现了功能集合4。
类4:男人类。继承自类3。新添加了"写程序"的功能。
类5:女人类。继承自类3。新添加了"发脾气"的功能。
做业:请你们把上面的关系用图画出来
OK,让咱们看下面的话,判断对错:
1. 男人是男人 (√) 缘由:原本就是!
2. 男人是人 (√) 缘由:人类是男人类的基类
3. 男人是灵长类动物 (√)缘由:灵长类是男人类的更抽象层基类
4. 男人是会说话的 (√) 缘由:男人类的基类实现了语言系统
5. 女人是猴子 (×) 缘由:若是我这么说,会被蹁死
6. 猴子是女人 (×) 缘由:女人不是猴子的基类
7. 人会写程序 (×)缘由:写程序方法是在男人类中才具体实现的
8. 女人会发脾气 (√) 缘由:由于我说5..
哈哈!如今你明白什么是多态了吧!实际上是很是简单的逻辑思惟。上面仅仅是多态的一个概念,下面咱们经过代码去研习一下程序中的多态究竟是什么。
不少公司在面试的时候常拿下面几个问题当开胃小菜:
1. 如何使用virtual和override?
2. 如何使用abstract和override?
3. "重写"与"重载"同样吗?
4. "重写"、"覆盖"、"隐藏"是同一个概念吗?
顺便说一句:若是你肯定能把上面的概念很熟练的掌握,发个Mail给我(bladey@tom.com ),也许你能收到一份薪水和福利都不错的Offer :p
今天咱们学习多态,其实就是解决问题1。前面已经提到过,多态机制是依靠继承机制实现的。那么,在常规继承机制的基础之上,在基类中使用virtual函数,并在其派生类中对virtual函数进行override,那么多态机制就天然而然地产生了。
小议virtual:
呵呵,我这人比较笨--有个人老师和同窗为证--学东西奇慢无比,因此当初在C++中学习virtual的历程是我心中永远挥之不去的阴影..倒霉就倒霉 在这个"虚"字上了。"实"的我还云里雾里呢,更况且这"虚"的,"虚"的还没搞清楚呢,"纯虚"又蹦出来了, 我#@$%!^#&&!..
还好,我挺过来了..回顾这段学习历程,我发现万恶之源就是这个"虚"字。
在汉语中,"虚"就是"无","无"就是"没有",没有的事情就"不可说"、"不可讲"--那还讲个X??老师也头疼,学生更头疼。拜初中语文老师所赐,个人语言逻辑还算过关,总感受virtual function译为"虚函数"有点词不达意。
找来词典一查,virtual有这样一个词条:
Existing or resulting in essence or effect though not in actual fact, form, or name:
实质上的,实际上的:虽然没有实际的事实、形式或名义,但在实际上或效果上存在或产生的:
例句:
the virtual extinction of the buffalo.
野牛实际上已经绝迹(隐含的意思是"尽管野牛还木有死光光,但从效果上来说..")
啊哦~~让我想起一句话:
有的人活着他已经死了; 有的人死了他还活着..
不由有点惊叹于母语的博大精深--
virtual function中的virtual应该译作"名不副实"而不是"虚"!
OK,下面就让咱们看看类中的virtual函数是怎么个"名不副实"法。
例子1: 非virtual / override程序
// 水之真谛 //
// http://blog.csdn.net/FantasiaX //
// 上善若水,润物无声 //
using System;
using System.Collections.Generic;
using System.Text;
namespace Sample
{
// 演员(类)
class Actor
{
public void DoShow()
{
Console.WriteLine("Doing a show...");
}
}
// 乐手(类),继承自Actor类
class Bandsman : Actor
{
// 子类同名方法隐藏父类方法
// 其实标准写法应该是:
// public new void DoShow(){...}
// 为了突出"同名",我把new省了,编译器会自动识别
public void DoShow()
{
Console.WriteLine("Playing musical instrument...");
}
}
// 吉他手(类),继承自Bandsman类
class Guitarist : Bandsman
{
public new void DoShow()
{
Console.WriteLine("Playing a guitar solo...");
}
}
class Program
{
static void Main(string[] args)
{
// 正常声明
Actor actor = new Actor();
Bandsman bandsman = new Bandsman();
Guitarist guitarist = new Guitarist();
// 通常状况下,随着类的承继和方法的重写
// 方法是愈来愈具体、愈来愈个性化
actor.DoShow();
bandsman.DoShow();
guitarist.DoShow();
Console.WriteLine("===========================");
//尝试多态用法
Actor myActor1 = new Bandsman(); //正确:乐手是演员
Actor myActor2 = new Guitarist(); //正确:吉他手是演员
Bandsman myBandsman = new Guitarist(); //正确:吉他手是乐手
//仍然调用的是引用类型自身的方法,而非派生类的方法
myActor1.DoShow();
myActor2.DoShow();
myBandsman.DoShow();
}
}
}
代码分析:
1. 一上来,演员类、乐手类、吉他手类造成一个继承链。
2. 乐手类和吉他手类做为子类,都把其父类的DoShow()方法"隐藏"了。
3. 特别强调:"隐藏"不是"覆盖",后面要讲的"重写"才是真正的"覆盖"。
4. 隐藏是使用new修饰符实现的,但这个修饰符能够省略。
5. 隐藏(Hide)的含意是:父类的这个函数实际上还在,只是被子类的同名"藏起来"了。
6. 重写(override)与覆盖是同一个含意,只是覆盖并不是编程的术语,但"覆盖"比较形象。
7. 主程序代码的上半部分是常规使用方法,没什么好说的。
8. 主程序代码的下半部分已经算是多态了,但因为没有使用virtual和override,多态最有价值的效果--个性化方法实现--没有体现出来。后面的例子专门体现这一点。
例子2: 应用virtual / override,真正的多态
// 水之真谛 //
// http://blog.csdn.net/FantasiaX //
// 上善若水,润物无声 //
using System;
using System.Collections.Generic;
using System.Text;
namespace Sample
{
// 演员(类)
class Actor
{
// 使用了virtual来修饰函数
// 此函数已经"名不副实"了
public virtual void DoShow()
{
Console.WriteLine("Doing a show...");
}
}
// 乐手(类),继承自Actor类
class Bandsman : Actor
{
// 使用了override来修饰函数
// 此函数将取代(重写)父类中的同名函数
public override void DoShow()
{
Console.WriteLine("Playing musical instrument...");
}
}
// 吉他手(类),继承自Bandsman类
class Guitarist : Bandsman
{
public override void DoShow()
{
Console.WriteLine("Playing a guitar solo...");
}
}
class Program
{
static void Main(string[] args)
{
// 正常声明
Actor actor = new Actor();
Bandsman bandsman = new Bandsman();
Guitarist guitarist = new Guitarist();
// 通常状况下,随着类的承继和方法的重写
// 方法是愈来愈具体、愈来愈个性化
actor.DoShow();
bandsman.DoShow();
guitarist.DoShow();
Console.WriteLine("===========================");
//尝试多态用法
Actor myActor1 = new Bandsman(); //正确:乐手是演员
Actor myActor2 = new Guitarist(); //正确:吉他手是演员
Bandsman myBandsman = new Guitarist(); //正确:吉他手是乐手
// Look!!!
// 调用的是引用类型所引用的实例的方法
// 引用类型自己的函数是virtual的
// 看似"存在",实际已经被其子类重写(不是隐藏,而是被kill掉了)
// 这正是virtual所要表达的"名不副实"的本意,而非一个"虚"字所能传达
myActor1.DoShow();
myActor2.DoShow();
myBandsman.DoShow();
}
}
}
代码分析:
1. 除了将继承链中最顶层基类的DoShow()方法改成用virtual修饰;把继承链中派生类的DoShow()方法改成override修饰以重写基类的方法。
2. 主程序代码没变,但下半部分产生的效果彻底不一样!请体会"引用变量自己方法"与"引用变量所引用实例的方法"的不一样--这是关键。
多态成因的分析:
为何会产生这样的效果呢?这里要提到一个"virtual表"的问题。咱们看看程序中继承链的构成:Actor à Bandsman à Guitarist。由于派生类不但继承了基类的代码(确切地说是public代码)并且还有本身的特有代码(不管是否是与基类同名,都是本身特有的)。 从程序的逻辑视角来看,你能够这样想象:在内存中,子类的实例所占的内存块是在父类所占的内存块的基础上"追加"了一小块--拜托你们本身画画图。这多出 来的一小块里,装的就是子类特有的数据和代码。
咱们仔细分析这几句代码:
1. Actor actor = new Actor(); //常规的声明及分配内存方法
由于类是引用类型,因此actor这个引用变量是放在栈里的、类型是Actor类型,而它所引用的实例--一样也是Actor类型的--内存由new操做 符来分配而且放在堆里。这样,引用变量与实例的类型如出一辙、彻底匹配。换句话说:栈里的引用变量所能"管理"的堆中的内存块大小正好、很少也很多。
2. Actor myActor1 = new Bandsman(); //正确:乐手是演员
一样是这句代码,在两个例子中产生的效果彻底不一样。为何呢?且看!在例1中,在Bandsman类中只是使用new将父类的DoShow()给隐藏了 --所起的做用仅限于本身对父类追加的代码块中,丝毫没有影响到父类。而栈中的引用变量是Actor类型的myActor1,它只能管理Actor类实例 所占的那么大一块内存,而对追加的内存毫无控制能力(或者说看不见追加的这块内存)。所以,当你使用myActor1.DoShow();调用成员方法 时,myActor1只能使唤本身能管到的那块内存里的DoShow()方法。那么例2中呢?难道例2中的myActor1就能管理追加的一块内存了吗?否也!它仍然管理不了,但不要忘了--这时候Actor类中的DoShow()方法已经被virtual所修饰,同时Bandsman类中的DoShow()方法已经被override修饰。这时候,当执行myActor1.DoShow();一句时,myActor1调用本身所管辖的内存块时,发现DoShow()这个 函数已经标记为"可被重写"了(其实,在VB.NET中,与C#的virtual关键字对应的关键字就是Overridable,更直白),那么它就会尝 试去发现有没有override链(也就是virtual表,即"虚表")的存在,若是存在,那么就调用override链上的最新可用版本--这就有了 咱们在例2中看到的效果。
3. Actor myActor2 = new Guitarist(); //正确:吉他手是演员
经过这句代码,你也能够想象一下2级重写是怎么造成的,同时也能够感悟一下所谓"重写链上最新的可用版本"是什么意思。
4. Guitarist myActor2 = new Actor(); //错误:想想为何?
呵呵,这是错误的,缘由是引用变量所管理的内存大小超出了实例实际的内存大小。
乱弹:
多态,台湾的兄弟们喜欢称"多型",同样的。"多"表示在实例化引用变量的时候,根据用户当时的使用状况(这时候程序已经Release了,不能再修改了,程序员已经不能控制程序了)智能地给出个性化的响应。
多,谓之变。莫非"多态"亦可称为"变态"耶?咦.."变型"..让我想起Transformer来了。
TO BE CONTINUE