为了解决这一问题,静态类型的面向对象语言通常被设计为可以向上转型:当给一个类变量赋值时,这个变量的类型既可以使用这个类本身,也可以使用这个类的超类。这就像我们在描述天上的一只麻雀或者一只喜鹊时,通常说“一只麻雀在飞”或者“一只喜鹊在飞”。但如果想忽略它们的具体类型,那么也可以说”一只鸟在飞“。
同理,当Duck对象和Chicken对象的类型都被隐藏在超类型Animal身后,Duck对象和Chicken对象就能被交换使用,这是让对象表现出多态性的必经之路,而多态性的表现正是实现众多设计模式的目标。
4. 使用继承得到多态效果
使用继承来得到多态效果,是让对象表现出多态性的最常用手段。继承通常包括实现继承和接口继承。本节我们讨论实现继承,接口继承的例子请参见第21章。
我们先创建一个Animal抽象类,再分别让Duck和Chicken都继承自Animal抽象类,下述代码中(1)处和(2)处的赋值语句显然是成立的,因为鸭子和鸡也是动物:
public abstract class Animal { abstract void makeSound(); //抽象方法 } public class Chicken extends Animal{ public void makeSound(){ System.out.println( 咯咯咯 ); } } public class Duck extends Animal{ public void makeSound(){ System.out.println( 嘎嘎嘎 ); } } Animal duck = new Duck(); //(1) Animal chicken = new Chicken(); //(2)
现在剩下的就是让AnimalSound类的makeSound方法接受Animal类型的参数,而不是具体的Duck类型或者Chicken类型:
public class AnimalSound{ public void makeSound( Animal animal ){ //接受Animal类型的参数 animal.makeSound(); } } public class Test { public static void main( String args[] ){ AnimalSound animalSound= new AnimalSound (); Animal duck = new Duck(); Animal chicken = new Chicken(); animalSound.makeSound( duck ); //输出嘎嘎嘎 animalSound.makeSound( chicken ); //输出咯咯咯 } }
5. JavaScript的多态
从前面的讲解我们得知,多态的思想实际上是把“做什么”和“谁去做”分离开来,要实现这一点,归根结底先要消除类型之间的耦合关系。如果类型之间的耦合关系没有被消除,那么我们在makeSound方法中指定了发出叫声的对象是某个类型,它就不可能再被替换为另外一个类型。在Java中,可以通过向上转型来实现多态。
而JavaScript的变量类型在运行期是可变的。一个JavaScript对象,既可以表示Duck类型的对象,又可以表示Chicken类型的对象,这意味着JavaScript对象的多态性是与生俱来的。
这种与生俱来的多态性并不难解释。JavaScript作为一门动态类型语言,它在编译时没有类型检查的过程,既没有检查创建的对象类型,又没有检查传递的参数类型。在2节的代码示例中,我们既可以往makeSound函数里传递duck对象当作参数,也可以传递chicken对象当作参数。
由此可见,某一种动物能否发出叫声,只取决于它有没有makeSound方法,而不取决于它是否是某种类型的对象,这里不存在任何程度上的“类型耦合”。这正是我们从上一节的鸭子类型中领悟的道理。在JavaScript中,并不需要诸如向上转型之类的技术来取得多态的效果。
6. 多态在面向对象程序设计中的作用
有许多人认为,多态是面向对象编程语言中最重要的技术。但我们目前还很难看出这一点,毕竟大部分人都不关心鸡是怎么叫的,也不想知道鸭是怎么叫的。让鸡和鸭在同一个消息之下发出不同的叫声,这跟程序员有什么关系呢?
Martin Fowler在《重构:改善既有代码的设计》里写到:
多态的最根本好处在于,你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为——你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。
换句话说,多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句。
Martin Fowler的话可以用下面这个例子很好地诠释:
在电影的拍摄现场,当导演喊出“action”时,主角开始背台词,照明师负责打灯光,后面的群众演员假装中枪倒地,道具师往镜头里撒上雪花。在得到同一个消息时,每个对象都知道自己应该做什么。如果不利用对象的多态性,而是用面向过程的方式来编写这一段代码,那么相当于在电影开始拍摄之后,导演每次都要走到每个人的面前,确认它们的职业分工(类型),然后告诉他们要做什么。如果映射到程序中,那么程序中将充斥着条件分支语句。