深刻剖析经典面试题之四:OOP的三个核心本质之多态

来源:互联网 发布:下载p2p软件 编辑:程序博客网 时间:2024/05/22 09:03

   OOP的三个核心本质是什么?

 

    这是道基础中见思想的面试题,面试官爱问这个问题。不过关于OOP三个核心众多书籍似乎莫衷一是。《Java编程思想》第7章谈到多态的开篇语便是:除了数据的抽象化(data abstraction)与继承(Inheritance)以外,面向对象编程语言的第三个核心本质便是多态(ploymorphism)。另有参考书把封装(wrap)、重载(overload)也归为OOP的特性。以上几点点确实都应算做OOP的特性,但是无论怎么组合我想多态是少不了的,如果语言不支持多态,就不能称之为面向对象的。有的语言只支持类而不支持多态,如AdaVB,我们称它们为基于对象的语言。有人总结说:封装是优点,继承是基础,重载是特点,而多态是特征。

 

1)什么是多态

 

多态是一种性质,应为多态性。最直白的理解就是:在程序运行时,根据类的对象来确定调用哪个函数。但是多态的意义不仅限于此,多态的意义更在于:子类型别向上转型为基类型别,从而通过一个公共函数来执行这些类中相同的方法。在C#中通过virtualabstract来实现,在Java中省掉了virtual。下面是引举的《Java编程思想》第7章中关于多态的一个例子:

 

//:Music4.java

 

//Abstract classes and methods

 

Import java.util.*;

 

 

abstract classInstrument4{

 

inti; //storage allocated for each

 

public abstract void play();

 

public String what(){

 

return  "Instrument4";

 

}

 

public abstract void adjust();

 

}

 

 

ClassWind4 extends Instrument4{

 

public void play(){

 

System.out.println("Wind4.play()");

 

}

 

public String what(){return"Wind4";}

 

public void adjust(){}

 

}

 

 

ClassPercussion4 extends Instrument4{

 

public void play(){

 

System.out.println("Percussion4.play()");

 

}

 

public String what(){return"Percussion4";}

 

public void adjust(){}

 

}

 

 

ClassStringed4 extends Instrument4{

 

public void play(){

 

System.out.println("Stringed4.play()");

 

}

 

public String what(){return"Stringed4";}

 

public void adjust(){}

 

}

 

 

ClassBrass4 extendsWind4{

 

public void play(){

 

System.out.println("Brass4.play()");

 

}

 

public void adjust(){

 

System.out.println("Brass4.adjust()");

 

}

 

}

 

 

class Woodwind4 extendsWind4{

 

public void play(){

 

System.out.println("Woodwind4.play()");

 

}

 

public String what(){return"Woodwind4";}

 

}

 

 

public class Music4{

 

//Doesn't care about type,so new types

 

//added to the system still work right:

 

Static void tune(Instrument4i){

 

//...

 

i.play();

 

}

 

static void tuneAll(Instrument4[]e){

 

for(int i=0;itune(e[i]);

 

}

 

public static void main(String[] args){

 

Instrument4[] orchestra = newInstrument4[5];

 

int i = 0;

 

//Upcasting  during addition to the array:

 

orchestra[i++]=new Wind4();

 

orchestra[i++]=new Percussion4();

 

orchestra[i++]=new Stringed4();

 

orchestra[i++]=new Brass4();

 

orchestra[i++]=new Woodwind4();

 

tuneAll(orchestra);

 

}

 

}///:~

 

2)多态的重要性

 

为什么要说“语言不支持多态,就不能称之为面向对象的”。

 

传统的多态实际上就是由虚函数(Virtual Function)利用虚表(Virtual Table)实现的,自然是离不开继承,换句话说多态实际上覆盖了继承。正是由于继承与多态的紧密联系,使得我们很容易张冠李戴,那么如何区别呢?

 

举个常用的例子:

 

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配置对象,大量使用接口采用所谓面向接口的方法。

 

3)多态的种类

 

 1、 基类继承多态(Base Class Polymorphism)

 

2、 接口继承多态(Interface Polymorphism

 

    多态并不仅仅局限于传统的虚函数,接口的使用也体现出多态的应用。具体的区分要从设计模式的论域(problem space)的角度来进行区分。我在博客的《从设计模式看抽象类与接口的区别》和《深刻剖析经典面试题之三:关于虚函数》中都有具体的提到。

 

4)多态的运行机制

 

所谓“绑定(binding)”,就是建立method call(函数调用)和method body(函数本体)的关联。如果绑定动作发生于程序执行前(由编译器和连接器完成),称为“先期绑定”。对于面向过程的语言它们没有其他选择,一定是先期绑定。比如C编译器只有一种method call,就是先期绑定。(C++有先期联编和后期联编)

 

当有多态的情况时,解决方法便是所谓的后期绑定(late binding):绑定动作将在执行期才根据对象型别而进行。后期绑定也被称为执行期绑定(run-time binding)或动态绑定(dynamic binding)。程序语言若想实现后期绑定,必须具备“得以在执行期判定对象型别”并“调用其相应之函数”的机制。也就是说,编译器仍然不知道对象的型别,但“method call”机制会找出正确的method body并加以调用。程序语言的后期绑定机制做法因人而异,但是你可以想象,必须有某种“型别信息”被置于对象内。

 

Java的所有函数,除了被声明为final者,都使用后期绑定。从表面上来理解,将函数声明为final型这样是为了防止他人覆写该函数。但是或许更重要的是,这么做可以“关闭”动态绑定。或者说,这么做便是告诉编译器:动态绑定是不需要的。于是编译器可以产生效率较佳的程序代码。这样说来,动态绑定(后期绑定)的效率不如先期绑定了。

 

这让我想起了我被面试官问的最惨的一次------他问我多态的运行机制,也就是说编译器是怎么识别不同的对象调用了不同类里的函数的。我以前并没仔细想过这个问题。然后面试官进一步暗示我说:“为什么我们有时候要把函数声明为final型的?”我答曰:“不想让别人覆写该函数。”面试官然后又不依不饶的追问道:“没有其他的原因了吗?”今天想来,怕是面试官追问的是:如果我们将函数声明为final后,就“关闭”了动态绑定,或许能得到较好的执行效率。但是《Java编程思想》又说了:“这样做并不会为你的程序带来整体效能的提升。所以最好是基于设计上的考量来决定是否使用final,而不要企图藉由它来改善性能。”真是学无止境啊,不将《Java编程思想》烂熟于心,只是找两本Java的教辅来看看的人怎么能应付面试官如此诡异的问题。我想不出意外,有天我也会成为PM级别以上的人,也会去面试新人。我想那时候我会以平常的心态去面对面试者,而不是卖弄自己的小聪明,给他人布下陷阱而显露出自己的高深。这也是我为什么要自己查资料翻阅书籍“剖析”“经典面试题”的原因,一方面是为了批判的继承这些面试题中的精华之处,将基础知识融会贯通。另一方面就是为了去批驳网上满天飞的面试题的不严谨的地方。

 

 

后记:对待一个问题我总希望能追寻到我能触及到的最深处,但是今天发现“昨天的认识”总是要比“今天的认识”肤浅,计算机技术博大精深,而我们绝大多数人却都只是在管中窥豹。今天想想,前几日的心态有点不太端正。我的总结应该是为了提高自己,而不是去征服别人。因为学无止境,今天总要比明天肤浅一些。

 

 

 

 

 

 

 

 

 

 

 

 

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 厨柜门上的板板掉了怎么办 衣柜门滑轮坏了怎么办 厨房推拉门推动时有声音是怎么办? 塑料推拉门声音大怎么办不好推 推拉门锁扣坏了怎么办 衣柜移门拉不动怎么办?如何保养 擦黑色桌面有层白灰怎么办 宝宝睡觉不盖被子怎么办 孩子盖被子就哭怎么办 一盖被子就发烧怎么办 两岁宝宝认被子盖怎么办 小孩吃多了发烧怎么办 两岁宝宝拉蛔虫怎么办 吃了长蛆的东西怎么办 被蜱虫咬了又找不到虫子怎么办 木家具生黑虫子怎么办 吃了发霉的面包怎么办 种的韭菜有蛆怎么办 活狗身上长蛆虫怎么办 狗身上会有蛆虫怎么办 狗身上长满了蛆怎么办 房间墙上有很多小虫子怎么办 床上有许多小虫子怎么办? 店里有许多小虫子怎么办 房间潮湿有很多小虫子怎么办 家里潮湿墙上发霉长小虫怎么办? 房间有小飞虫子怎么办 狗被灭虫剂喷了怎么办 吃鸡玩久了手机屏幕很涩怎么办 超东卧室太阳晒怎么办 床头上的布破了怎么办 老年机全静音了怎么办 老年机手机不亮怎么办 70岁老人耳朵聋怎么办 血压太低了头晕怎么办 血压高忽然变低怎么办 血压高眼睛红了怎么办 高血压200降不下去.怎么办 高血压吃药降不下来怎么办 合肥房子卖了户口怎么办 吃了粽子胃难受怎么办