多态(1):向上转型

    在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。

    多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。多态不但能够改善代码的组织结构和可读性,还能够创建可扩展的程序--即无论在项目最初创建时还是在需要添加新功能时都可以“生长”的程序。

    “封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。这种类型的组织机制对那些拥有过程化程序设计背景的人来说,更容易理解。而多态的作用则是消除类型之间的耦合关系。在前面我们已经知道,继承允许将对象视为它自己本身的类型或其基类型来加以处理。这种能力极为重要,因为它允许将多种类型(从同一基类导出的)视为同一类型来处理,而同一份代码也就是可以毫无差别地运行在这些不同类型之上了。多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是从同一基类导出而来的。这种区别是根据方法行为的不同而表示出来的,虽然这些方法都可以通过同一个基类来调用。

一、向上转型

    对象既可以作为它自己本身的类型使用,也可以作为它的基类型使用。而这种把对某个对象的引用视为对其基类型的引用的做法被称作向上转型--因为在继承树的画法中,基类是放置在上方的。

    但是,这样做也有一个问题,具体看下面这个有关乐器的例子。

    首先,既然几个例子都要演奏乐符(Note),我们就应该在包中单独创建一个Note类。

/**
* 乐符    
*/
public enum Note {
	MIDDLE_C, C_SHARP, B_FLAT;
}

    在这里,Wind是一种Instrument,因此可以从Instrument类继承。

/**
 * 乐器
 */
class Instrument {
	public void play(Note n) {
		System.out.println("Instrument的play()" + n);
	}
}

/**
 * 吹响
 */
public class Wind extends Instrument {
	public void play(Note n) {
		System.out.println("Wind的play()" + n);
	}
}
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);
	}
}

    Music.tune()方法接受一个Instrument引用,同时也接受任何导出自Instrument的类。在main()方法中,当一个Wind引用传递到tune()方法时,就会出现这种情况,而不需要任何类型转换。这样做是允许的--因为Wind从Instrument继承而来,所以Instrument的接口必定存在于Wind中。从Wind向上转型到Instrument可能会“缩小”接口,但不会比Instrument的全部接口更窄。

二、忘记对象类型

    Music.java看起来似乎有些奇怪。为什么所有人都故意忘记对象的类型呢?在进行向上转型时,就会产生这种情况;并且如果让tune()方法直接接受一个Wind引用作为参数,似乎会更为直观。但这样引发的一个重要问题是:如果那样做,就需要为系统内Instrument的每种类型都编写一个新的tune()方法。假设按照这种推理,现在再加入Stringed(弦乐)和Brass(管乐)这两种Instrument(乐器):

/**
 * 弦乐
 */
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);
	}
}

    这样做行得通,但有一个主要缺点:必须为添加的每一个新Instrument类编写特定类型的方法。这意味着在开始时就需要更多的编程,这样意味着如果以后想添加类似tune()的新方法,或者添加自Instrument导出的新类,仍需要做大量的工作。此外,如果我们忘记重载某个方法,编译器不会返回任何错误信息,这样关于类型的整个处理过程就变得难以操纵。

    如果我们只写这样一个简单方法,它仅接收基类作为参数,而不是那些特殊的导出类。这样做情况会变得更好吗?也就是说,如果我们不管导出类的存在,编写的代码只是与基类打交道,会不会更好呢?

    这正是多态所允许的。然而,大多数程序员具有面向过程程序设计背景,对多态的运作方式可能会有一点迷惑。

 如果本文对您有很大的帮助,还请点赞关注一下。

发布了100 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_40298351/article/details/104141932