首页 > 代码库 > JAVA编程思想(4) - 多态(一)

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的类。例如WindInstrument的导出类,那么当Wind引用传递到tune()方法式,就会出现这种情况,而不需要任何类型转换。这么做是允许的——因为WindInstrument继承而来,所以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()方法完全忽略了它周围代码所发生的变化,依旧正常运行,这正是我们期望多态所具有的特性。多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。



JAVA编程思想(4) - 多态(一)