JAVA编程思想(4) - 多态(一)
多态
- 在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本类型。
- 多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能够创建可扩展程序。
再论向上转型
代码
//: polymorphism/music/Note.java
// Notes to play on musical instruments.
package polymorphism.music;
public enum Note {
MIDDLE_C, C_SHARP, B_FLAT; // Etc.
} ///:~
//: polymorphism/music/Instrument.java
package polymorphism.music;
import static net.mindview.util.Print.*;
class Instrument {
public void play(Note n) {
print("Instrument.play()");
}
}
///:~
//: polymorphism/music/Wind.java
package polymorphism.music;
// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
// Redefine interface method:
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
} ///:~
//: polymorphism/music/Music.java
// Inheritance & upcasting.
package polymorphism.music;
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); // Upcasting
}
} /* Output:
Wind.play() MIDDLE_C
*///:~
- Music.tune()方法接受一个Instrument引用,同时也接受任何导出自Instrument的类。例如Wind是Instrument的导出类,那么当Wind引用传递到tune()方法式,就会出现这种情况,而不需要任何类型转换。这么做是允许的——因为Wind从Instrument继承而来,所以Instrument接口必定存在于Wind中,从Wind向上转型到Instrument可能会“缩小”接口,但不会比Instrument的全部接口更窄。
忘记对象类型
- 为什么所有人都故意忘记对象的类型呢?在进行向上转型的时候,就会产生这样的情况 ;如果让tune()方法接受一个Wind引用作为自己的参数,似乎会更加直观。但是这样会引发一个重要的问题:如果那样做的话,就需要为Instrument的每种类型都编写一个新的tune方法。假设这样,我们再加入个新的类型的时候就要大量的增加代码。
- 举个例子
//: polymorphism/music/Music2.java
// Overloading instead of upcasting.
package polymorphism.music;
import static net.mindview.util.Print.*;
class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
}
class Brass extends Instrument {
public void play(Note n) {
print("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); // No upcasting
tune(violin);
tune(frenchHorn);
}
} /* Output:
Wind.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
*///:~
-
这样做是可以,但是有一个缺点:必须为添加每一个新的Instrument类编写特定类型的方法。这意味着在开始的时候就需要更多的编程,同时在你以后添加类似tune()的新方法或者添加自Instrument导出的新类,仍需要做大量的工作。此外,如果我们忘记重载某个方法,编译器不会放回任何错误信息,这样关于类型的整个处理过程将变得难以控制。
-
如果我们只写这样一个简单方法,它仅接受基类作为参数,而不是那些特殊的导出类。这样做情况会变好吗?也就是说,如果我们不管导出类的存在,编写的代码只是与基类打交道,会不会更好?而这正是多态所允许的。
转机
- 观察一段代码
public static void tune(Instrument i) {
// ...
i.play(Note.MIDLE_C);
}
- 它接受的是Instrument引用,那么在这种情况下,编译器是怎么知道这个Instrument引用的指向是Wind对象,而不是Brass对象或者其他呢,实际上,编译器是无法得知的,而这就涉及到了绑定。
方法调用绑定
- 将一个方法调用同一个方法主体关联起来被称为绑定。如在程序执行前进行绑定,就叫做前期绑定。这是面向过程的语言中不需要选择就默认的绑定方式。例如,C++只有一种方法调用,那就是前期绑定。
- 但是这并不足以结果上面代码的困惑,解决的办法是后期绑定,它的含义是在运行时根据对象的类型进行绑定,后期绑定也叫做动态绑定或运行时绑定。在这里,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。后期绑定机制随编译语言的不同而有所不同,不管怎样都必须在对象中安置某种”类型信息”。
- Java除了Static方法和final方法(private方法属于final)之外,其他所有的方法都是后期绑定,这意味着通常情况下,我们不必判定是否进行后期绑定————它会自动发生。
- 将某个方法声明为final方法会有效的“关闭”动态绑定,这样,编译器就会为final方法调用生成更有效的代码。
产生正确的行为
- 一旦知道Java中所有方法都是通过动态绑定来实现多态这个事实后,我们就可以编写只与基类打交道的程序代码了,并且这些代码对所有的导出类都可以正确运行。
//: polymorphism/shape/Shape.java
package polymorphism.shape;
public class Shape {
public void draw() {}
public void erase() {}
} ///:~
//: polymorphism/shape/Circle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Circle extends Shape {
public void draw() { print("Circle.draw()"); }
public void erase() { print("Circle.erase()"); }
} ///:~
//: polymorphism/shape/Triangle.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Triangle extends Shape {
public void draw() { print("Triangle.draw()"); }
public void erase() { print("Triangle.erase()"); }
} ///:~
//: polymorphism/shape/Square.java
package polymorphism.shape;
import static net.mindview.util.Print.*;
public class Square extends Shape {
public void draw() { print("Square.draw()"); }
public void erase() { print("Square.erase()"); }
} ///:~
//: polymorphism/shape/RandomShapeGenerator.java
// A "factory" that randomly creates shapes.
package polymorphism.shape;
import java.util.*;
public class RandomShapeGenerator {
private Random rand = new Random(47);
public Shape next() {
switch(rand.nextInt(3)) {
default:
case 0: return new Circle();
case 1: return new Square();
case 2: return new Triangle();
}
}
} ///:~
//: polymorphism/Shapes.java
// Polymorphism in Java.
import polymorphism.shape.*;
public class Shapes {
private static RandomShapeGenerator gen =
new RandomShapeGenerator();
public static void main(String[] args) {
Shape[] s = new Shape[9];
// Fill up the array with shapes:
for(int i = 0; i < s.length; i++)
s[i] = gen.next();
// Make polymorphic method calls:
for(Shape shp : s)
shp.draw();
}
} /* Output:
Triangle.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Circle.draw()
*///:~
- 这段代码就是典型的多态应用了。
可扩展性
- 由于有了多态机制,我们可以根据自己的需求对系统添加任意多的新类型,而不需要修改代码。在一个良好的OOP程序中,大多数或者所有的方法都是只与基类接口通信的。这样的程序是可扩展的,因为我们可以从通用的基类继承出新的数据类型,从而新添一些功能,那些操作基类接口的方法不需要任何的改动就可以应用于新类。
- 我们再来看看Instrument这个例子。
//: polymorphism/music3/Music3.java
// An extensible program.
package polymorphism.music3;
import polymorphism.music.Note;
import static net.mindview.util.Print.*;
class Instrument {
void play(Note n) { print("Instrument.play() " + n); }
String what() { return "Instrument"; }
void adjust() { print("Adjusting Instrument"); }
}
class Wind extends Instrument {
void play(Note n) { print("Wind.play() " + n); }
String what() { return "Wind"; }
void adjust() { print("Adjusting Wind"); }
}
class Percussion extends Instrument {
void play(Note n) { print("Percussion.play() " + n); }
String what() { return "Percussion"; }
void adjust() { print("Adjusting Percussion"); }
}
class Stringed extends Instrument {
void play(Note n) { print("Stringed.play() " + n); }
String what() { return "Stringed"; }
void adjust() { print("Adjusting Stringed"); }
}
class Brass extends Wind {
void play(Note n) { print("Brass.play() " + n); }
void adjust() { print("Adjusting Brass"); }
}
class Woodwind extends Wind {
void play(Note n) { print("Woodwind.play() " + n); }
String what() { return "Woodwind"; }
}
public class Music3 {
// Doesn‘t care about type, so new types
// added to the system still work right:
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);
}
public static void tuneAll(Instrument[] e) {
for(Instrument i : e)
tune(i);
}
public static void main(String[] args) {
// Upcasting during addition to the array:
Instrument[] orchestra = {
new Wind(),
new Percussion(),
new Stringed(),
new Brass(),
new Woodwind()
};
tuneAll(orchestra);
}
} /* Output:
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
*///:~
- 事实上,不需要改动tune()方法,所有的新类都能与原有的类一起正确运行,即使tune()是单独存放在某个文件中,并且Instrument接口中添加了其他的新方法,tune()也不需要再编写就能正确运行。
- 可以看到,tune()方法完全忽略了它周围代码所发生的变化,依旧正常运行,这正是我们期望多态所具有的特性。多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。