多形性

对于面向对象的程序设计语言,多型性是第三种最基本的特征,前两种是数据抽象和继承。

“多形性”(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么”与“怎样做”两个模块的分离。利用多形性的概念,代码的组织以及可读性均能获得改善。此外,还能创建“易于扩展”的程序。无论在项目的创建过程中,还是在需要加入新特性的时候,它们都可以方便地“成长。

1.上溯造型

将一个对象作为它自己的类型使用,或者作为它的基础类型的一个对象使用。取得

一个对象句柄,并将其作为基础类型句柄使用的行为就叫作“上溯造型”——因为继承树的画法是基础类位于最上方。

package c07;
class Note 
{
  private int value;
  private Note(int val) { value = val; }
  public static final Note
  middleC = new Note(0),
  cSharp = new Note(1),
  cFlat = new Note(2);
}
class Instrument 
{
  public void play(Note n) {
  System.out.println("Instrument.play()");
  }
}
// Wind objects are instruments
// because they have the same interface:
class Wind extends Instrument
 {
  // Redefine interface method:
  public void play(Note n) {
  System.out.println("Wind.play()");
  }
}
public class Music
 {
  public static void tune(Instrument i) {
  // ...
  i.play(Note.middleC);
  }
  public static void main(String[] args) {
  Wind flute = new Wind();
  tune(flute); // Upcasting
  }
}

其中,方法 Music.tune()接收一个Instrument 句柄,同时也接收从Instrument 衍生出来的所有东西。当一个Wind 句柄传递给 tune()的时候,就会出现这种情况。

为什么要上溯造型

这个程序看起来也许显得有些奇怪。为什么所有人都应该有意忘记一个对象的类型呢?进行上溯造型时,就可能产生这方面的疑惑。而且如果让tune()简单地取得一个Wind 句柄,将其作为自己的自变量使用,似乎会更加简单、直观得多。但要注意:假如那样做,就需为系统内Instrument 的每种类型写一个全新的tune()。

假设按照前面的推论,加入 Stringed(弦乐)和 Brass(铜管)这两种Instrument(乐器):

......
public class Music2 
{
  public static void tune(Wind2 i) {
  i.play(Note2.middleC);
  }
  public static void tune(Stringed2 i) {
  i.play(Note2.middleC);
  }
  public static void tune(Brass2 i) {
  i.play(Note2.middleC);
  }
  public static void main(String[] args) {
  Wind2 flute = new Wind2();
  Stringed2 violin = new Stringed2();
  Brass2 frenchHorn = new Brass2();
  tune(flute); // No upcasting
  tune(violin);
  tune(frenchHorn);
  }
}

这样写会增加大量代码,类型的整个操作过程就显得极难管理,有失控的危险。

2.深入理解

对于Music.java 的困难性,可通过运行程序加以体会。输出是Wind.play()。这当然是我们希望的输出,但它看起来似乎并不愿按我们的希望行事。

它接收Instrument 句柄。所以在这种情况下,编译器怎样才能知道 Instrument句柄指向的是一个 Wind,而不是一个Brass 或Stringed 呢?编译器无从得知。为了深入了理解这个问题,我们有必要探讨一下“绑定”这个主题。

2.1方法调用的绑定

将一个方法调用同一个方法主体连接到一起就称为“绑定”(Binding)。

解决的方法就是“后期绑定”,它意味着绑定在运行期间进行,以对象的类型为基础。后期绑定也叫作“动态绑定”或“运行期绑定”。

Java 中绑定的所有方法都采用后期绑定技术,除非一个方法已被声明成final。这意味着我们通常不必决定是否应进行后期绑定——它是自动发生的。

为什么要把一个方法声明成final 呢?正如final的使用中指出的那样,它能防止其他人覆盖那个方法。但也许更重要的一点是,它可有效地“关闭”动态绑定,或者告诉编译器不需要进行动态绑定。这样一来,编译器就可为final 方法调用生成效率更高的代码。

2.2产生正确的行为

在面向对象的程序设计中,有一个经典的“形状”例子。由于它很容易用可视化的形式表现出来,所以经常都用它说明问题。

形状例子有一个基础类,名为Shape;另外还有大量衍生类型:Circle(圆形),Square(方形),Triangle(三角形)等等。

上溯造型可用下面这个语句简单地表现出来:

Shape s = new Circle();

2.3扩展性

现在,让我们仍然返回乐器(Instrument)示例。由于存在多形性,所以可根据自己的需要向系统里加入任意多的新类型,同时毋需更改tune()方法。在一个设计良好的 OOP程序中,我们的大多数或者所有方法都会遵从tune()的模型,而且只与基础类接口通信。我们说这样的程序具有“扩展性”,因为可以从通用的基础类继承新的数据类型,从而新添一些功能。如果是为了适应新类的要求,那么对基础类接口进行操纵的方法根本不需要改变,

对于乐器例子,假设我们在基础类里加入更多的方法,以及一系列新类,下面是示意图:

所有这些新类都能与老类——tune()默契地工作,毋需对tune()作任何调整。即使 tune()位于一个独立的文件里,而将新方法添加到Instrument 的接口,tune()也能正确地工作,不需要重新编译。下面这个程序是对上述示意图的具体实现:

import java.util.*;
class Instrument3 
{
  public void play() {
  System.out.println("Instrument3.play()");
  }
  public String what() {
  return "Instrument3";
  }
  public void adjust() {}
 }
class Wind3 extends Instrument3 
{
  public void play() {
  System.out.println("Wind3.play()");
  }
  public String what() { return "Wind3"; }
  public void adjust() {}
}
class Percussion3 extends Instrument3
 {
  public void play() {
  System.out.println("Percussion3.play()");
  }
  public String what() { return "Percussion3"; }
  public void adjust() {}
}
class Stringed3 extends Instrument3
 {
  public void play() {
  System.out.println("Stringed3.play()");
  }
  public String what() { return "Stringed3"; }
  public void adjust() {}
}
class Brass3 extends Wind3
 {
  public void play() {
  System.out.println("Brass3.play()");
  }
  public void adjust() {
  System.out.println("Brass3.adjust()");
  }
}
class Woodwind3 extends Wind3
 {
  public void play() {
  System.out.println("Woodwind3.play()");
  }
  public String what() { return "Woodwind3"; }
}
public class Music3
 {
  // Doesn‘t care about type, so new types
  // added to the system still work right:
  static void tune(Instrument3 i) {
  // ...
  i.play();
  }
  static void tuneAll(Instrument3[] e) {
  for(int i = 0; i < e.length; i++)
  tune(e[i]);
  }
  public static void main(String[] args) {
  Instrument3[] orchestra = new Instrument3[5];
  int i = 0;
  // Upcasting during addition to the array:
  orchestra[i++] = new Wind3();
  orchestra[i++] = new Percussion3();
  orchestra[i++] = new Stringed3();
  orchestra[i++] = new Brass3();
  orchestra[i++] = new Woodwind3();
  tuneAll(orchestra);
  }
}

新方法是what()和adjust()。前者返回一个 String句柄,同时返回对那个类的说明;后者使我们能对每种乐器进行调整。

在main()中,当我们将某样东西置入Instrument3数组时,就会自动上溯造型到 Instrument3。

可以看到,在围绕tune()方法的其他所有代码都发生变化的同时,tune()方法却丝毫不受它们的影响。

3.覆盖与过载

“过载”是指同一样东西在不同的地方具有多种含义;而“覆盖”是指它随时随地都

只有一种含义,只是原先的含义完全被后来的含义取代了。

时间: 2024-10-19 16:22:01

多形性的相关文章

9.JAVA编程思想 多形性

欢迎转载,转载请标明出处:http://blog.csdn.net/notbaron/article/details/51040241 "对于面向对象的程序设计语言,多型性是第三种最基本的特征(前两种是数据抽象和继承." "多形性"(Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了"是什么"与"怎样做"两个模块的分离.利用多形性的概念,代码的组织以及可读性均能获得改善.此外,还能创建"

php设计模式及耦合性和多形性

什么是设计模式: 设计模式就是一个教你如何利用真实可靠的设计来组织你的代码的模板. 所有的设计模式都有一些常用的特性:一个标识(a name),一个问题陈述(a problem statement)和一个解决方案(a solution). 1.一个设计模式的标识是重要的,因为它会让其他的程序员不用进行太深入的学习就能立刻理解你的代码的目的(至少通过这个标识程序员会很熟悉这个模式). 2.问题描述是用来说明这个模式的应用的领域. 3.解决方案描述了这个模型的执行.一个好的设计模式的论述应该覆盖使用

读TIJ -7 多形性

<Think in java·第 7 章  多形性> [面向对象的程序设计语言三种最基本的特征:数据抽象.继承和多态] 在这个层面是没有什么"思想"好谈的!当你按照人们熟悉的.习惯的思维方式,去思考"构造和组织"程序时,你可能会觉得很自然--你具有面向对象的思想:或者,有人X按照人们熟悉的.习惯的(人的而非机器的)思维方式,给你介绍.解释数据抽象.继承和多态时,你觉得贴近生活和人的思考习惯.你觉得他讲的概念理所当然.自然(当然也浅显易懂),这是X在&qu

php各种设计模式简单实践思考

前言 我一直觉得什么框架,版本,甚至语言对于一个coder来说真的不算什么,掌握一个特别高大上的一个框架或者是一个新的,少众的语言真的不算什么,因为你可以,我要花时间也可以,大家都是这样的.所以基本的显得额外重要,即是算法和数据结构,再就是好的设计模式了,,,听过一句话,是好的数据结构是让计算机更快的工作,而一个好的设计模式则是使开发者工作的更快! 单例模式 单例模式特点 $_instance 必须声明为静态的私有变量 构造函数和克隆函数必须声明为私有的,这是为了防止外部程序 new 类从而失去

java设计模式--工厂模式

总结 (1)简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的. (2)工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成. (3)抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类.它针对的是有多个产品的等级结构.而工厂方法模式针对的是一个产品的等级结构. 一.工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的. 工厂模式在<Java

thinking in java ----reading note (1)

# thinking in java 4th# reading note# victor# 2016.02.10 chapter 1 对象入门 1.1 抽象的进步    (1) 所有东西都是对象.    (2) 程序是一大堆对象的组合,对象间通过消息联系.    (3) 通过封装现有对象,可制作出新型对象.    (4) 每个对象都有一种类型(某个类的实例).    (5) 同一类的所有对象都能接受相同的消息.    1.2 对象的接口 & 1.3 实现方法的隐藏     接口规定了可对一个特定

单例模式和工厂模式(百度文库)

模式中的单例模式分为:饿汉模式和懒汉模式. 顾名思义: 饿汉模式是对食物(Singleton)比较渴望的,所有一开始就new了一个来满足(食欲) 饿汉式: public class Singleton{   private static Singleton singleton = new Singleton ();   private Singleton (){}   public static  Singleton getInstance(){return singletion;}   } 饿

设计模式-工厂模式

一.工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的. 工厂模式在<Java与模式>中分为三类:1)简单工厂模式(Simple Factory):不利于产生系列产品: 2)工厂方法模式(Factory Method):又称为多形性工厂: 3)抽象工厂模式(Abstract Factory):又称为工具箱,产生产品族,但不利于产生新的产品:             这三种模式从上到下逐步抽象,并且更具一般性.             GOF在<

Java ClassLoader深入讲解(转)

当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构: bootstrap classloader                |       extension classloader                |       system classloader bootstrap classloader -引导(也称为原始)类加载器,它负责加载Java的核心类.在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用 - D选