第八章 多态(上)
在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。
多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来,多态不但能改善代码的组织结构和可读性,还能够创建可扩展的程序,无论在项目最初创建时还是在需要添加新功能时都可以生长程序。封装通过合并特征和行为来创建新的数据类型。
8.1 再论向上转型
在上一章中我们已经知道,对象可以作为它本身的类型使用,也可以作为它的基类使用,而这种把对某个对象的引用视为对其基类的引用的做法被称为向上转型。
//: demo/Note.java package com.example.demo; public enum Note { MIDDLE_C, C_SHARP, B_FLAT; } //: demo/Instrument.java package com.example.demo;
public class Instrument { public void play(Note n) { System.out.println("Instrument.play()"); } } //: demo/Wind.java package com.example.demo;
public class Wind extends Instrument { public void play(Note n) { System.out.println("Wind.play()" + n); } } //: demo/Music.java package com.example.demo; public class Music { public static void tune(Instrument i) { i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); tune(flute); } }
输出结果为:
Wind.play()MIDDLE_C
导出类可以接受基类的所有接口,向上转型可能会使接口“缩小”,但不会比基类的全部接口更窄。
8.1.1 忘记对象类型
package com.example.demo; class Stringed extends Instrument { public void play(Note n) { System.out.println("Stringed.play() " + n); } } class Brass extends Instrument { public void play(Note n) { System.out.println("Brass.play() " + n); } } public class Music2{ public static void tune(Wind i) { i.play(Note.MIDDLE_C); } public static void tune(Stringed i) { i.play(Note.MIDDLE_C); } public static void tune(Brass i) { i.play(Note.MIDDLE_C); } public static void main(String[] args) { Wind flute = new Wind(); Stringed violin = new Stringed(); Brass frenchHorn = new Brass(); tune(flute); tune(violin); tune(frenchHorn); } }
输出结果为:
Wind.play()MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
如果我们忘记重载某个方法,编译器不会返回任何错误信息,这样关于类型的整个处理过程就变得难以操纵。如果我们只写一个简单方法,它只接受基类作为参数,而不是那些特殊的导出类,会不会更好?这正是多态所允许的,然而大多数程序员具有面向过程程序设计的背景,对多态的运作方式可能会有一点迷惑。
8.2 转机
8.2.1 方法调用绑定
将一个方法调用同一个方法主体关联起来被称作绑定。如果在程序执行前进行绑定,叫做前期绑定。上面的程序之所以令人费解,主要是因为前期绑定,当编译器只有一个Instrument引用时,它无法知道究竟调用哪个方法才对。解决方法是后期绑定,他的含义就是在运行时根据对象的类型进行绑定。
Java中除了static方法和final方法之外,其他所有方法都是后期绑定,这意味着通常情况下我们不必判定是否应该进行后期绑定,它也会自动发生。调用final可以防止其他人覆盖该方法,但更重要的一点是,这样可以有效关闭动态绑定,这样编译器就可以为final调用生成更有效的代码,
8.2.2 产生正确的行为
一旦知道Java中所有方法都是通过动态绑定实现多态这是事实之后,我们就可以编写只与基类打交道的代码程序,并且这些代码对所有导出类都可以正确运行。
在几何形状这个例子中,有一个基类Shape以及多个导出类,下面的继承图展现他们之间的关系:
向上转型可以像下面这条语句这么简单:
Shape s = new Circle();
这里创建了一个Circle对象并把得到的引用赋值给Shape,这看似错误但实际上是没问题的,因为通过继承,Circle就是一种Shape。
假设你调用一个基类方法,并且它在导出类中被覆盖:
s.draw();
你可能再次认为调用的是Shape的draw()方法,因为这毕竟是一个Shape引用,那么编译器是怎么样知道去做其他事情的呢?由于后期绑定,也就是多态,还是正确调用了Circle.draw()方法。
Shape基类为从它那里继承而来,也就是说,所有的行传都可以描绘和删除,导出类通过覆盖这些定义,来为每种特殊类型的几何形状提供单独的行为。
8.2.3 可扩展性
现在我们返回到乐器示例。由于有多态机制,我们可以根据自己的需求对系统添加任意多的新类型,而不需要该tune()方法。在一个设计良好的OOP程序中,大多数方法都会遵循tune()的模型,而且只与基类接口通信。这样的程序时可扩展的,因为可以从通用的基类继承出新的数据类型从而添加一些功能,那些草丛基类接口的方法不需要任何改动就可以应用于新类。
我们向乐器的基类添加更多的方法,并加入一些新类:
事实上,不需要改动tune()方法,所有新类都能与原有类一起正确运行,及时tune()是单独放在某个文件中,并且在Instrument接口中添加了其他的新方法,tune()也不需要编译就能正确运行。下面是上图的具体实现:
原文:https://www.cnblogs.com/parable/p/11470203.html