Merge pull request #103 from xiangflight/master

[feat 09](截至 陷阱:覆盖私有方法)
This commit is contained in:
LingCoder
2019-07-05 08:56:02 +08:00
committed by GitHub
3 changed files with 293 additions and 0 deletions

View File

@@ -178,6 +178,299 @@ Java 中除了 **static** 和 **final** 方法(**private** 方法也是隐式
### 产生正确的行为
一旦当你知道 Java 中所有方法都是通过后期绑定来实现多态时,就可以编写只与基类打交道的代码,而且代码对于派生类来说都能正常地工作。或者换种说法,你向对象发送一条消息,让对象自己做正确的事。
面向对象编程中的经典例子是形状 **Shape**。这个例子很直观,但不幸的是,它可能让初学者困惑,认为面向对象编程只适合图形化程序设计,实际上不是这样。
形状的例子中,有一个基类称为 **Shape** ,多个不同的派生类型分别是:**Circle****Square****Triangle** 等等。这个例子之所以好用,是因为我们可以直接说“圆(Circle)是一种形状(Shape)”,这很容易理解。继承图展示了它们之间的关系:
![形状继承图](../images/1562204648023.png)
向上转型就像下面这么简单:
```java
Shape s = new Circle();
```
这会创建一个 **Circle** 对象,引用被赋值给 **Shape** 类型的变量 s这看似错误将一种类型赋值给另一种类型然而是没问题的因此从继承上可认为圆(Circle)就是一个形状(Shape)。因此编译器认可了赋值语句,没有报错。
假设你调用了一个基类方法(在派生类中都被覆写):
```java
s.draw()
```
你可能再次认为 **Shape**`draw()` 方法被调用,因为 s 是一个 **Shape** 引用——编译器怎么可能知道要做其他的事呢?然而,由于后期绑定(多态)被调用的是 **Circle**`draw()` 方法,这是正确的。
下面的例子稍微有些不同。首先让我们创建一个可复用的 **Shape** 类库,基类 **Shape** 中建立了所有继承自 **Shape** 的类的共同接口——所有的形状都可以被绘画和擦除:
```java
// polymorphism/shape/Shape.java
package polymorphism.shape;
public class Shape {
public void draw() {}
public void erase() {}
}
```
派生类通过覆写这些方法为每个具体的形状提供独一无二的方法行为:
```java
// polymorphism/shape/Circle.java
package polymorphism.shape;
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Circle.draw()");
}
@Override
public void erase() {
System.out.println("Circle.erase()");
}
}
// polymorphism/shape/Square.java
package polymorphism.shape;
public class Square extends Shape {
@Override
public void draw() {
System.out.println("Square.draw()");
}
@Override
public void erase() {
System.out.println("Square.erase()");
}
}
// polymorphism/shape/Triangle.java
package polymorphism.shape;
public class Triangle extends Shape {
@Override
public void draw() {
System.out.println("Triangle.draw()");
}
@Override
public void erase() {
System.out.println("Triangle.erase()");
}
}
```
**RandomShapes** 是一种工厂,每当我们调用 `get()` 方法时,就会产生一个指向随机创建的 **Shape** 对象的引用。注意,向上转型发生在 **return** 语句中,每条 **return** 语句取得一个指向某个 **Circle****Square** 或 **Triangle** 的引用, 并将其以 **Shape** 类型从 `get()` 方法发送出去。因此无论何时调用 `get()` 方法,你都无法知道具体的类型是什么,因为你总是得到一个简单的 **Shape** 引用:
```java
// polymorphism/shape/RandomShapes.java
// A "factory" that randomly creates shapes
package polymorphism.shape;
import java.util.*;
public class RandomShapes {
private Random rand = new Random(47);
public Shape get() {
switch(rand.nextInt(3)) {
default:
case 0: return new Circle();
case 1: return new Square();
case 2: return new Triangle();
}
}
public Shape[] array(int sz) {
Shape[] shapes = new Shape[sz];
// Fill up the array with shapes:
for (int i = 0; i < shapes.length; i++) {
shapes[i] = get();
}
return shapes;
}
}
```
`array()` 方法分配并填充了 **Shape** 数组,这里使用了 for-in 表达式:
```java
// polymorphism/Shapes.java
// Polymorphism in Java
import polymorphism.shape.*;
public class Shapes {
public static void main(String[] args) {
RandomShapes gen = new RandomShapes();
// Make polymorphic method calls:
for (Shape shape: gen.array(9)) {
shape.draw();
}
}
}
```
输出:
```
Triangle.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Circle.draw()
```
`main()` 方法中包含了一个 **Shape** 引用组成的数组,其中每个元素通过调用 **RandomShapes** 类的 `get()` 方法生成。现在你只知道拥有一些形状,但除此之外一无所知(编译器也是如此)。然而当遍历这个数组为每个元素调用 `draw()` 方法时,从运行程序的结果中可以看到,与类型有关的特定行为奇迹般地发生了。
随机生成形状是为了让大家理解:在编译时,编译器不需要知道任何具体信息以进行正确的调用。所有对方法 `draw()` 的调用都是通过动态绑定进行的。
### 可扩展性
现在让我们回头看音乐乐器的例子。由于多态机制,你可以向系统中添加任意多的新类型,而不需要修改 `tune()` 方法。在一个设计良好的面向对象程序中,许多方法将会遵循 `tune()` 的模型,只与基类接口通信。这样的程序时可扩展的,因为可以从通用的基类派生出新的数据类型,从而添加新的功能。那些操纵基类接口的方法不需要改动就可以应用于新类。
考虑一下乐器的例子,如果在基类中添加更多的方法,并加入一些新类,将会发生什么呢:
![乐器继承图](../images/1562252767216.png)
所有的新类都可以和原有类正常运行,不需要改动 `tune()` 方法。即使 `tune()` 方法单独存放在某个文件中,而且向 **Instrument** 接口中添加了新的方法,`tune()` 方法也无需再编译就能正确运行。下面是类图的实现:
```java
// polymorphism/music3/Music3.java
// An extensible program
// {java polymorphism.music3.Music3}
package polymorphism.music3;
import polymorphism.music.Note;
class Instrument {
void play(Note n) {
System.out.println("Instrument.play() " + n);
}
String what() {
return "Instrument";
}
void adjust() {
System.out.println("Adjusting Instrument");
}
}
class Wind extends Instrument {
@Override
void play(Note n) {
System.out.println("Wind.play() " + n);
}
@Override
String what() {
return "Wind";
}
@Override
void adjust() {
System.out.println("Adjusting Wind");
}
}
class Percussion extends Instrument {
@Override
void play(Note n) {
System.out.println("Percussion.play() " + n);
}
@Override
String what() {
return "Percussion";
}
@Override
void adjust() {
System.out.println("Adjusting Percussion");
}
}
class Stringed extends Instrument {
@Override
void play(Note n) {
System.out.println("Stringed.play() " + n);
}
@Override
String what() {
return "Stringed";
}
@Override
void adjust() {
System.out.println("Adjusting Stringed");
}
}
class Brass extends Wind {
@Override
void play(Note n) {
System.out.println("Brass.play() " + n);
}
@Override
void adjust() {
System.out.println("Adjusting Brass");
}
}
class Woodwind extends Wind {
@Override
void play(Note n) {
System.out.println("Woodwind.play() " + n);
}
@Override
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);
}
}
```
输出:
```
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
```
新方法 `what()` 返回一个带有类描述的 **String** 引用,`adjust()` 提供一些乐器调音的方法。
`main()` 方法中,当向 **orchestra** 数组添加元素时,元素会自动向上转型为 **Instrument**
`tune()` 方法可以忽略周围所有代码发生的变化,仍然可以正常运行。这正是我们期待多态能提供的特性。代码中的修改不会破坏程序中其他不应受到影响的部分。换句话说,多态是一项“将改变的事物与不变的事物分离”的重要技术。
### 陷阱:”覆写“私有方法
<!-- Constructors and Polymorphism -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB