多态(Polymorphism)
-- 面向对象的典征,现代软件设计的基石

All rights reserved.
Samuel
12.12.2004 first published on
www.cnblogs.com/samuel

前言

   如果让你选择一项面向对(Object Oriented,后文简称OO)象最重要的或者最能表现OO特点的技术特征,会是什么?


   封装(wrap)、继承(inheritance)、重载(override)还是多态(polymorphism),亦或是其他?
在我看来,答案无疑将是多态。封装是优点,继承是基础,重载是特点,而多态则是特征。


  虽然这四者缺一不可,无论少了哪一个,就像一个人缺胳膊少腿,使OO将不再是完整的,但是前三者对于OO来说好比鼻子耳朵,而多态则是生殖器,没有多态的
OO就象是被阉割的雄性,已经失去其典征。

 

什么是多态?

  简单来说,多态是具有表现多种形态的能力的特征,在OO中是指,语言具有根据对象的类型以不同方式处理之,特别是重载方法和继承类这种形式,的能力。多态被认为是面向对象语言的必备特性。

多态有多种分类,通过了解这些分类可以更丰满对其认识,在这里就不再罗列,请各位参考 wiki大百科 javaworld .

 


多态与泛型(generic)

  多态实际上就是泛型。


   所谓泛型就是指我们不为特定的类型进行专门编码,而采用对不同类型进行通用编码的方式,无论是数据结果还是算法。


  传统的泛型是指类似以Template function的方式使参数一般化,典型的应用是C++ STL,比如List、Vector以及algorithm。


  而OO已能通过接口(Interface)和抽象类(Abstract Class)进行真正意义上的泛型了。在我看来,这就是OO最精彩的地方,也就是多态的威力。而对于传统意义上的Generic,我始终觉得其作用已经今不如昔了。

 

多态和继承(Inheritance)

  严格来说,多态与继承、重载并不是孤立的,他们之间存在着紧密的联系,多态是建立在这两者的基础之上的(实际上继承就有用重载这一特性)。


  传统的多态实际上就是由虚函数(Virtual Function)利用虚表(Virtual Table)实现的(早期C模拟OO特性时使用最多,C++的实现也是,后来的技术未作研究,是
否使用VT不得而知),自然是离不开继承,换句话说多态实际上覆盖了继承。

  正是由于继承与多态的紧密联系,使得我们很容易张冠李戴,那么如何区别呢?

  举个常用的例子:

Abstract Class Sharp implement IHaveSide {
 public bool isSharp(){
   return true;
 }
 public abstract int getSides();
}

Class Triangle extends Sharp {
 public override int getSides() {
   return 3;
 }
}

Class Rectangle extends Sharp {
 pubilc override int getSides() {
  return 4;
 }
}

那么这种类的关系叫做继承,下面这种使用方式也是继承所带来的:
Triangel tri = new Triangle();
println("Triangle is a type of sharp? " + tri.isSharp());

而这种方式则是多态:
Sharp sharp = new Rectangle();
println("My sharp has " + sharp.getSides() + " sides.");

这两者区别在哪?很显然,继承是子类使用父类的方法,而多态则是父类使用子类的方法。

其技术上的区别是绑定时期,晚期绑定一定是多态。

这里介绍区别并不是想说茴字有几种写法,而是只有清楚的认识到技术的显著特点后才能更好的使用它。

 

现代软件设计

  现代软件大量的使用框架、模式(非特指Deisgn Pattern),也就是将软件开发的一些共性进行抽象,提出普遍适用的软件结构。


  无论是框架还是模式,他们都有一些明显的共同点 — 使用xml配置对象,大量使用接口采用所谓面向接口的方法,利用反射实现。

  为什么要接口?因为需要抽象,需要将未知的对象在已有的框架中表现。


  如何实现接口?多态!所谓反射,实际上就是一种晚期绑定的技术,这个技术实质上表现出来的就是多态这一特征。

  面向方面开发(Aspect Oriented Programming)是一个热点,也是现代软件发展的趋势。定制、组件装配的软件开发方式在应用越来越复杂、需求变化越来越快的今天显得日趋重要。那么如何才能使今天的软件能够适应明天需要呢?如何使我开发速度更快?如何能更容易的修改应用?AOP则是解决这些问题的有效手段。


  让我们看看框架容器的主要模式,Inversion of Control Containers(IoC)/Dependency Injection(包括setter injection, construct injection, interface
injection等),其主要好处就是类之间的依赖,通过运行期的查找来进行绑定。那么他的基础是什么呢?还是多态!


  我们可以看到,在现代软件的开发中,无数的思想象火花一样跳动。其中一类很重要的思想就是建立在多态这样一个很基本的特性,甚至可以说是一个语言概念之上的。在这里希望通过这篇文章抛砖引玉,引起更多的对与当今软件发展发向的思考,同时探究其根源。

 

后记

行文简陋,错漏在所难免,愿与诸位探讨

 

参考
wiki大百科  
http://en.wikipedia.org/wiki/Polymorphism_(computer_science)
java世界    
http://www.javaworld.com/javaworld/javatips/jw-javatip30.html
Matin Fowler http://www.martinfowler.com/

Posted on 2004-12-12 20:13 Samuel Chen 阅读(9986) 评论(23) 编辑 收藏

Feedback

#1楼  回复 引用 查看   

2004-12-12 21:20 by idior      
呵呵,怎么说呢?我也明白你的思想。
可能因为我看了 design patterns explain的原因(它那里面没怎么谈多态这个名词,不过多态在面向对象中肯定是处处再用啦),其实我更多的是想指出从继承和多态中能看到封装的含义。
(注意我对封装的解释)不然,只认为封装是对私有数据的隐藏,就很难明白下面这句话。
find what vary and encapsulation it


不过你对多态的解释很不错 :)

#2楼  回复 引用 查看   

2004-12-13 08:54 by wayfarer      
按照Fowler的说法,动态是OOP中最高贵的特性!OOP中很重要的一点是面向接口编程,通过接口将具体实现抽象出来,再根据多态的形式,进行晚绑定,以决定具体的实现。

确实如你所说,多态在OOP中非常重要。而是否能应用好,也是你能否体现面向对象思想的关键。

btw:不知为什么?我访问你的blog时,总有一部分正文看不到,也不知道是什么原因。是你博客样式的问题吗?

#3楼[楼主]  回复 引用 查看   

2004-12-13 09:33 by Samuel      
to idior:
大家都理解就能更好的探讨了。你提到的design pattern explain我没有看过,但是对于把这些特性都看作具有封装的意义,虽然确实是隐藏了,但是感觉还不妥当,呵呵,持不同意见。
你的两篇乱弹都看了,写的很不错啊,是很好的OO建模思路。

to wayfarer:
Fowler 那篇文章叫什么名字来着?可得看看。
我没有用样式阿,具体是怎么样看不到?
是不是某篇文章不能自动断行造成的?

#4楼  回复 引用   

2004-12-13 12:36 by Ninputer[未注册用户]
我认为多态和泛型多少还是有区别的,可能不仅仅是编译时还是运行时的问题。
比如用.NET泛型可以做到这样的代码,而用多态却做不到

Sub Test(Of T As {IComparable, IDisposable})(arg As T)
  If arg.CompareTo(xxx) Then arg.Dispose()
End Sub

#5楼  回复 引用 查看   

2004-12-13 13:25 by wayfarer      
打开你的博客都这样,我也不知道什么原因。但如果你的文章是放到博客园首页或团队首页上,就没有问题。

所以,我写的comment都是在记事本上写好,再粘贴上去的。

Flower说的这个,是在其书《Refactorying》中写到的:) Replace case statement with Polymorphism条款。

#6楼  回复 引用 查看   

2004-12-13 13:27 by wayfarer      
btw: Design to interfaces.

Find what varies and encapsulate it.

Favor composition over inheritance

z这三句话是很出名的,对于理解OOP很有帮助.

#7楼[楼主]  回复 引用 查看   

2004-12-15 11:24 by Samuel      
to Ninputer :
刚看到你这个例子,汗,这语法没见过-_-;静下心看看,还是能明白。
像这样的方式好像传统的C++的Template不可以的,不知是不是.Net新增加的能力?
尝试着想用IDisposeAfterCompare来完成同样的工作,可是突然又想到A impl IDisposable, B extends A, C extends B impl ICompare 这样的形式。
不过我认为,完全可以抛开generic而在OO的上设计去规避掉或者说是实现这样的要求。
需要时间来考虑这个东西,这两天加班没时间,也许过段时间考虑好了再放一篇post与诸君探讨,望Ninputer兄若有所思亦能post之

#8楼[楼主]  回复 引用 查看   

2004-12-15 11:29 by Samuel      
to wayfarer:

Thanks.
不知道其他的tx有没有这样的问题,如果都没有可能是你的cache中的css作怪,如果一定数量的都有,也许我该换个skin了。

Refactorying大名如雷贯耳,可惜未能拜读。可能是我认为更重要或者我更喜欢的是,也是我将重心放到了Architecture和Framework,虽然它们也离不开refactory的理念。

那三句话,多态、封装、继承;)

#9楼  回复 引用   

2005-01-09 22:11 by Alexis
不错,不过
重载 overload

#10楼  回复 引用   

2005-03-11 16:10 by myx
多态像一位机司在公共汽车上,他可能是一位乖客,也可能是一位司机^o^

#11楼  回复 引用   

2005-05-29 00:11 by jetz
其实侯捷的《深入浅出MFC》中对多态性的解释是我见过最容易理解的。

#12楼  回复 引用   

2005-12-11 16:24 by yf[未注册用户]
你似乎没有搞清楚多态、重写、重载的关系。多态包括重写和重载,重写就是将父类的方法重新实现一遍,重载是同一个类中可以有相同名字的方法,只要参数不同就可以(语言不同,具体的规定不同)。

“为什么要接口?因为需要抽象,需要将未知的对象在已有的框架中表现。”接口的原始用途是用于封装方法。将未知的对象在已有的框架中表现,只是使用接口的一个理由或技巧。这就像刀是用来砍东西的,我们发现它也可以用来装饰,现在你却硬要说刀就是用来装饰的。

“所谓反射,实际上就是一种晚期绑定的技术,这个技术实质上表现出来的就是多态这一特征。”反射不是晚期绑定的技术,它只是可以用于实现晚期绑定。反射是通过编译器查找与唯一指定的类相对应的声明的过程。因为我们平时是先声明,然后编译,反射就是反过来。我更不明白它和多态有什么关系,难道没有多态就不能实现反射。

面向方面开发不是也不能用于面向未知或未来,AOP只是让程序员懂得如何去更好地设计系统的类体系。它属于构架设计的范畴。因为从对象抽象类可以是千姿百态的,AOP告诉我们应该把系统构架设计从类体系设计中独立出来。我不知道这和多态又有什么关系。

我不知道什么叫“父类使用子类的方法”,这个例子说的是对象的方法调用,连最起码的类和对象之间的关系都没搞清楚,还妄谈什么。儿子从父亲那儿继承了东西,那就是他自己的,怎么还是父亲的?

既然继承了就可以使用,你如果不把继承的东西发扬光大,那么看起来好像是(记住不是,使用父亲的要明确写明,比如super.XXX())在使用父亲的;如果你把它发扬光大,那就是重写了。如果给你不同的条件,你可以烧不同的菜(不同的方法烧),但说起来你终归是在烧菜,那就是重载。

总之继承和多态只是类的一种规则,他和编程的方法和技巧没有什么关系,严格地说他和面向对象也没关系。


#13楼[楼主]  回复 引用 查看   

2005-12-12 10:21 by Samuel      
to yf:

很高兴能和你讨论,不过我感觉你有些过于咬文嚼字了。

你第二句话就完全暴露了你对OO的理解,“多态包括重写和重载”,首先,重载和重写是一样的,是大陆和台湾的不同说法,其次,多态和重载没有什么关系,要说有也是重载帮助多态实现的关系,多态实际是一种思想,比较虚的。

你的第二段,请问类是干什么的?按你的说法,接口启不是脱裤子放屁。

第三段,"反射不是晚期绑定的技术,它只是可以用于实现晚期绑定",这句话就和筷子不是吃饭的工具,它只是可以用于吃饭。其实我这句话有不严谨的地方,应该这么说好些“反射是可以实现晚期绑定的一种技术”,也许你就不会那么咬文嚼字了。

第四段,我都不知道怎么说了,我被你打败了 *_*

第五段,这是我有疏漏的地方,没有把类和对象写的很明确。但是,这是一篇纯理论的文章,基本上都是玩虚的(虚函数的虚),所以你要是不觉得“父类的对象的方法使用未知的子类的对象的方法”这样的句子拗口,我可以考虑下次这么写。

欢迎讨论,但反对孔乙己。

#14楼  回复 引用   

2005-12-13 14:42 by lyan[未注册用户]
楼上的那位,“首先,重载和重写是一样的,是大陆和台湾的不同说法”,God!!这怎么可能一样,跟大陆和台湾又有什么关系,这并不是你把程序写成程式照样可以理解的问题!!
我不知道楼上的精通什么语言,但OOP应该原理都是一样的,只是具体实现方式不同而已。我想可能楼主看的可能是某位四级没有过就靠翻译混口饭吃的老兄翻译的英文书吧,我不知道override,overload这两个概念在中文中怎么翻译,我是把他理解成覆盖和重载,关于这两个概念不用多做解释了,楼上的楼上那位已经解释的不错了。
不过楼主关于polymorphism的生殖器理论还是比较新颖的,不知道是不是原创,呵呵

#15楼[楼主]  回复 引用 查看   

2005-12-13 15:41 by Samuel      
楼上的,我很久没有看过技术书了。
看了那位同学对重载和重写的解释,就是说override和overload,所以那句话是我说错了。
但是,我的观点还是那样,override是帮助Polymophism实现的,8是他说的包含关系。
另外,即使没有overload,也不会对polymophism有什么影响的,也就是说,实现一个polymophism的具体应用时,overload完全可以8用。
最后,生殖器的比喻系原创。

#16楼  回复 引用   

2006-01-18 00:01 by alviso[未注册用户]
我在这里看到一篇文章,大致应该可以解释两位对overload和override的讨论

#17楼  回复 引用   

2006-03-19 22:46 by happy_david[未注册用户]
3、 多态与Overload概念的矫正
什么是多态(Polymorphisn)?多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal是通过虚函数(Virtual Function)实现的。接着是“虚函数”(或者是“虚方法”)。虚函数就是允许被其子类重新定义的成员函数。而子类重新定义父类虚函数的做法,称为“覆盖”(override),或者称为“重写”。
这里有一个经常混淆的概念。覆盖(override)和重载(overload)。覆盖是指子类重新定义父类的虚函数的做法。而重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!

#18楼[楼主]  回复 引用 查看   

2006-03-20 10:02 by Samuel Chen      
to happy_david:

Very good! Definitely right.

#19楼  回复 引用   

2006-05-22 11:25 by Seraph___Holibut[未注册用户]
关于多态与继承,原文看似对的,也有助于记忆。但细节之处确实不对。因为sharp对象实际不是Sharp类型(当然可以看成),它是Rectancle类型,所以它还是使用自己的getSides()方法。
只不过,看上去,象是(因为它要以看成Sharp类型)父类的对象使用了子类的方法。

所以yf的指正还是很有道理的。

另外,关于OO,Find what varies and encapsulate it. 还是很重要的思想。不谋则合,按Applying UML and Patterns上所提供的GRASP pattern,确实,PV(Protected Variations)是极为重要的思想。各种细节技术,大都是为了PV而设计的。

#20楼  回复 引用   

2006-06-17 17:11 by redpower[未注册用户]
to happy_david:
正确的解释,支持
也建议文章作者虚心听取happy_david的正确见解

另外对范型的理解也有些问题,建议文章作者参看相关书籍。

#21楼  回复 引用   

2006-06-22 14:10 by 不错呀[未注册用户]
首先谢谢楼主,我觉得说得非常的好,以至于我明白了多态就是编译时的不定态!希望以后再多贴一些见解出来,让我们学习!

#22楼  回复 引用   

2006-08-27 22:00 by zjsflyer[未注册用户]
多态和范型是个东西,岂能混为一谈? 楼主在这个方面了解得太少了...

不过通过设计,的确是可以不用范型...但反过来说,通过合理的设计,完全可以不用什么OO,用基于过程的函数就可以解决一切工程问题...

OO的出现,是为了解决基于过程的设计和编程的一些固有问题...但没有那一个现实世界的软件是只能用OO,不能用基于过程的设计和编程来解决的...

范型,AOP都是对OO的发展和某些缺点的弥补,你的软件不用范型,不等于不需要范型,AOP...

就我自己的感觉来说,我觉得OO与范型是互补的,没有谁取代谁的关系...简单第说,在用范型比用OO便利的地方就用范型,反之就用OO.

#23楼  回复 引用   

2007-06-26 01:34 by alanxyz[未注册用户]
说的真深,看不懂了。

隔行如隔山啊。

不知道这些理解能否联系到日常生活乃至哲学?