什么是多态(转)

一.什么是多态(Polymorphism

多态(Polymorphism)是面向对象(Object-OrientedOO)思想"三大特征"之一,其余两个分别是封装(Encapsulation)和继承(Inheritance--可见多态的重要性。或者说,不懂得什么是多态就不能说懂得面向对象。

 

多态是一种机制、一种能力,而非某个关键字。它在类的继承中得以实现,在类的方法调用中得以体现。

 

先让我们看看MSDN里给出的定义:

 

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,不难为大家了,我用几个句子说明一下多态的思想。

 

我们先把前文中提到的"接口"理解为"一组功能的集合",把""理解为功能的实现体。这样的例子多了去了。我们就拿生物界做比喻了:

 

功能集合:呼吸系统

 

功能集合:血液循环系统

 

功能集合:神经系统

 

功能集合:语言系统

 

类:灵长类动物。此类实现了到功能集合。

 

类:猴子类。继承自类。新添加了"爬树"的功能。

 

类:人类。继承自类。同时实现了功能集合。

 

类:男人类。继承自类。新添加了"写程序"的功能。

 

类:女人类。继承自类。新添加了"发脾气"的功能。

 

作业:请大家把上面的关系用图画出来

 

OK,让我们看下面的话,判断对错:

 

1. 男人是男人 原因:本来就是!

 

2. 男人是人 原因:人类是男人类的基类

 

3. 男人是灵长类动物 )原因:灵长类是男人类的更抽象层基类

 

4. 男人是会说话的 原因:男人类的基类实现了语言系统

 

5. 女人是猴子 × 原因:如果我这么说,会被蹁死

 

6. 猴子是女人 × 原因:女人不是猴子的基类

 

7. 人会写程序 ×)原因:写程序方法是在男人类中才具体实现的

 

8. 女人会发脾气 原因:因为我说..

 

哈哈!现在你明白什么是多态了吧!其实是非常简单的逻辑思维。上面仅仅是多态的一个概念,下面我们通过代码去研习一下程序中的多态到底是什么。

 

二.多态的基础--虚函数(virtual)和重写(override

很多公司在面试的时候常拿下面几个问题当开胃小菜:

 

1. 如何使用virtualoverride

 

2. 如何使用abstractoverride

 

3. "重写""重载"一样吗?

 

4. "重写""覆盖""隐藏"是同一个概念吗?

 

顺便说一句:如果你确定能把上面的概念很熟练的掌握,发个Mail给我(bladey@tom.com ),也许你能收到一份薪水和福利都不错的Offer :p

 

今天我们学习多态,其实就是解决问题。前面已经提到过,多态机制是依靠继承机制实现的。那么,在常规继承机制的基础之上,在基类中使用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函数是怎么个"名存实亡"法。

 

例子: 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. 主程序代码的下半部分已经算是多态了,但由于没有使用virtualoverride,多态最有价值的效果--个性化方法实现--没有体现出来。后面的例子专门体现这一点。

 

例子: 应用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(); //正确:乐手是演员

同样是这句代码,在两个例子中产生的效果完全不同。为什么呢?且看!在例中,在Bandsman类中只是使用new将父类的DoShow()给隐藏了--所起的作用仅限于自己对父类追加的代码块中,丝毫没有影响到父类。而栈中的引用变量是Actor类型的myActor1,它只能管理Actor类实例所占的那么大一块内存,而对追加的内存毫无控制能力(或者说看不见追加的这块内存)。因此,当你使用myActor1.DoShow();调用成员方法时,myActor1只能使唤自己能管到的那块内存里的DoShow()方法。那么例中呢?难道例中的myActor1就能管理追加的一块内存了吗?否也!它仍然管理不了,但不要忘了--这时候Actor类中的DoShow()方法已经被virtual所修饰,同时Bandsman类中的DoShow()方法已经被override修饰。这时候,当执行myActor1.DoShow();一句时,myActor1调用自己所管辖的内存块时,发现DoShow()这个函数已经标记为"可被重写"了(其实,在VB.NET中,与C#virtual关键字对应的关键字就是Overridable,更直白),那么它就会尝试去发现有没有override链(也就是virtual表,即"虚表")的存在,如果存在,那么就调用override链上的最新可用版本--这就有了我们在例中看到的效果。

 

 

 

3. Actor myActor2 = new Guitarist(); //正确:吉他手是演员

通过这句代码,你也可以想象一下级重写是怎么形成的,同时也可以感悟一下所谓"重写链上最新的可用版本"是什么意思。

 

4. Guitarist myActor2 = new Actor(); //错误:想一想为什么?

呵呵,这是错误的,原因是引用变量所管理的内存大小超出了实例实际的内存大小。

 

乱弹:

 

多态,台湾的兄弟们喜欢称"多型",一样的。""表示在实例化引用变量的时候,根据用户当时的使用情况(这时候程序已经Release了,不能再修改了,程序员已经不能控制程序了)智能地给出个性化的响应。

 

多,谓之变。莫非"多态"亦可称为"变态"耶?咦.."变型"..让我想起Transformer来了。

 

一.多态的现实意义

如果一个编程元素没有可以应用在软件工程中的现实意义,那将是一件不可容忍的事情。同理,如果你不了解一个编程元素的现实意义、不知道在编程时应该怎么用,就不能说自己懂得了这个编程元素。

我的编程经验实在不多,就我个人感觉,多态最大的现实意义在于代码的简化

多态为什么能简化代码捏?

先让我们用一句话概括多态的实现:首先要一个人父类,在这个父类的成员中,有一个virtual的(可以被子类重写的)方法。然后,有N多子类继承了这个父类,并且用override重写了父类的那个virtual方法——此时已经形成了一个扇形的多态继承图。当然,如果用作父类的是一个接口,那么在子类中就不是重写方法,而是实现方法了。

一旦这个继承扇形成了,我们应该意识到——无论是父类还是子类,他们都有一个同名的方法,而且此同名方法在各个子类中是个性化——它重写了父类的方法、并且子类与子类之间的这个同名方法也各不相同。

在程序的编写期,程序员总要预期用户可能进行的各种操作——比如对继承扇中每个子类的操作。程序编译完成、成为可执行文件并交付用户后,程序员就不能再控制程序了,这时候程序只能听从用户的摆布。假设没有多态,那么为了让用户在调用每个子类的时候程序都能有正确的响应,程序员不得不为每个子类在内存中创建一个实例——这样一来,程序复杂度增加的同时,性能也下降了。还好,这只是个假设……

OK,让我们还是来拿代码说事儿吧。下面给出两段代码,对比显示了多态的巨大优越性。

 

代码:非多态排比代码

 

//                          水之真谛                                    //

//            http://blog.csdn.net/FantasiaX               //

//                   上善若水,润物无声                            //

using System;

using System.Collections.Generic;

using System.Text;

namespace Sample

{

     class OptimusPrime                    //博派老大擎天柱

     {

         public void Transform()

         {

              Console.WriteLine("Transform to a TRUCK...");

         }

     }

     class Megatron                          //狂派老大威震天

     {

         public void Transform()

         {

              Console.WriteLine("Transform to a GUN...");

         }

     }

     class Bumblebee                        //大黄蜂

     {

         public void Transform()

         {

              Console.WriteLine("Transform to a CAR...");

         }

     }

     class Starscream                       //红蜘蛛

     {

         public void Transform()

         {

              Console.WriteLine("Transform to a FIGHTER...");

         }

     }

     class Program                           //主程序类

     {

         static void Main(string[] args)

         {

              string number = string.Empty;

 

              //为每个类准备一个实例

              OptimusPrime transformer1 = new OptimusPrime();

              Megatron transformer2 = new Megatron();

              Bumblebee transformer3 = new Bumblebee();

              Starscream transformer4 = new Starscream();

              while (true)                   //无限循环

              {

                   Console.WriteLine("Please input 1/2/3/4 to choose a transformer...");

                   number = Console.ReadLine();

                   switch (number)      //根据用户选择,作出响应

                   {

                   case "1":

                       transformer1.Transform();

                       break;

                   case "2":

                       transformer2.Transform();

                       break;

                   case "3":

                       transformer3.Transform();

                       break;

                   case "4":

                       transformer4.Transform();

                       break;

                   default:

                       Console.WriteLine("Do you want a TRACTOR ??");

                       break;

                   }

              }

         }

     }

}

代码分析:

1.        一上来是个独立的类(相信这位人物大家都不陌生吧……),这个类有一个同名方法:Transform()。虽然同名,但各自的实现却是个性化的、完全不同的——我们这里只用输出不同的字符串来表示,但你想啊——同样是有胳膊有腿的一个大家伙,变成汽车的方法跟变成飞机、变成枪怎么可能一样呢?

2.        进入主程序后,先是为每个类实例化一个对象出来,以备用户自由调用。这么做是很占内存的,如果为了优化程序,对每个类的实例化是可以挪到switch的每个case分支里的。

3.        一个无限循环,可以反复输入数字……

4.        switchcase根据用户的需求来调用合适的TransformerTransform方法

 

代码:使用多态,简化代码

using System;

using System.Collections.Generic;

using System.Text;

namespace Sample

{

     class Transformer                                      //基类

     {

         public virtual void Transform()

         {

              Console.WriteLine("Transform to a ??? ???...");

         }

     }

     class OptimusPrime : Transformer            //博派老大擎天柱

     {

         public override void Transform()

         {

              Console.WriteLine("Transform to a TRUCK...");

         }

     }

     class Megatron : Transformer                       //狂派老大威震天

     {

         public override void Transform()

         {

              Console.WriteLine("Transform to a GUN...");

         }

     }

     class Bumblebee : Transformer                     //大黄蜂

     {

         public override void Transform()

         {

              Console.WriteLine("Transform to a CAR...");

         }

     }

     class Starscream : Transformer                    //红蜘蛛

     {

         public override void Transform()

         {

              Console.WriteLine("Transform to a FIGHTER...");

         }

     }

     class Program                                            //主程序类

     {

         static void Main(string[] args)

         {

              string number = string.Empty;

              //只准备一个变量即可,并且不用实例化

              Transformer transformer;

              while (true)                                   //无限循环

              {

                   Console.WriteLine("Please input 1/2/3/4 to choose a transformer...");

                   number = Console.ReadLine();

                   switch (number)                       //根据用户选择,作出响应,运行期"动态"实例化

                   {

                   case "1":

                       transformer = new OptimusPrime();

                       break;

                   case "2":

                       transformer = new Megatron();

                       break;

                   case "3":

                       transformer = new Bumblebee();

                       break;

                   case "4":

                       transformer = new Starscream();

                       break;

                   default:

                       transformer = null;

                       break;

                   }

 

                   if (transformer != null)               //这里是本程序的核心

                   {

                       transformer.Transform();

                   }

                   else

                   {

                       Console.WriteLine("Do you want a TRACTOR ??");

                   }

              }

         }

     }

}

 

 

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值