详解JS面向对象的三大特征之多态

一、多态的定义

  多态是同一个行为具有多个不同表现形式或形态的能力。在JAVA中,多态通过在子类中重写父类方法去实现。但是在JS中,由于JS本身是动态的,天生就支持多态。大家可以通过几个例子来理解一下。

  多态实际上是不同对象作用于同一操作产生不同的效果

  多态的思想实际上是把“想做什么”和“谁去做“分开,多态的好处是什么呢?为什么要多态?我们来看看Martin Fowler 在《重构:改善既有代码的设计》里写到:多态的最根本好处在于,你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为,你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。 换句话说,多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句 。

二、多态的实现

  举个例子吧,国王听腻了只有鸭子为他唱歌,他决定搞一个动物合唱团。所以,大臣们搜罗了鸭,鸡,狗等动物,而且还设置了专门的选拔官员测试,选拔官员一声令下:‘唱’,面前的动物就发出了特有的叫声,鸭子嘎嘎嘎,小鸡咯咯咯,小狗汪汪汪......要实现这个功能,我们可以使用如下代码

var singStart(animal){
    if (animal instanceof Duck) {
        console.log('嘎嘎嘎');
    } else if (animal instanceof Chicken) {
        console.log('咯咯咯');
    }
}
function Duck(){}
function Chicken(){}
singStart(new Duck());  // 嘎嘎嘎
singStart(new Chicken());  // 咯咯咯

  这种方法当然也可以实现多态,但是却违反了封装性,我们将可变的动物类型与不可变的唱歌指令耦合到了一起。如果动物类型增加,我们必须在开始唱歌方法中新增判断分支。这就好比是选拔官员发出的指令是这样的:“你是鸭子的话,唱嘎嘎嘎,是鸡的话,唱咯咯咯,是狗的话,唱汪汪汪......”这明显是不合理的。真实的情况应该是,选拔官员发出简短清晰的指令“唱”时,每种动物会场出自己独有的声音。 让

  我们用面向对象的思想去考虑,将不变的指令隔离开来,将可变的具体实现封装起来。

  JAVA会使用类继承和重写的方式来实现,如下:

abstract class Animal {  
    abstract void sing();  
}  
  
class Duck extends Animal {  
    public void sing() {  
        System.out.println("嘎嘎嘎");  
    }  
}  
  
class Chicken extends Animal {  
    public void sing() {  
        System.out.println("咯咯咯");  
    }  
} 
public class Test {
    public static void main(String[] args) {
        Animal duck = new Duck();
        Animal chicken = new Chicken();
        singStart(duck);  // 嘎嘎嘎
        singStart(chicken);  // 咯咯咯
  }  
            
public static void singStart(Animal a)  {
      a.sing();
}

  而对于JS来讲,我们为具体的动物类型的原型定义具体的sing方法即可,如下:

function Duck(){}
Duck.prototype.sing = function(){
    console.log('嘎嘎嘎');
}
function Chicken(){}
Chicken.protorype.sing = function(){
    console.log(‘咯咯咯’);
}
function singStart(animal){
    animal.sing();
}
singStart(new Duck());  // 嘎嘎嘎
singStart(new Chicken());  // 咯咯咯

  而且,更棒的是,JS是动态的,这里并不限制传入的参数类型是animal。大家可以看到,我们在代码中,也没有实现Animal这个类型,事实上,我们可以传入任意类型的对象,只要它正确包含一个sing方法即可。如下:

function Person(){}
Person.protorype.sing = function(){
    console.log(‘哈哈哈’);
}
singStart(new Person());  // 哈哈哈

  可以看出来,由于JS本身的动态性,在JS中实现多态更加方便,且更加强大。再看下一个例子:

  假设我们要编写一个地图应用,现在有两家可选的地图 API 提供商供我们接入自己的应用。 就像我们接入一个地图APi的调用

var googleMap = {
    show: function () {
        console.log('开始渲染谷歌地图');
    }
};
var renderMap = function () {
    googleMap.show();
};
renderMap(); // 输出:开始渲染谷歌地图 

  由于某些原因,我们要换一下其他的API接口,为了让 renderMap 函数保持一定的弹性, 我们用一些条件分支来让 renderMap 函数同时支持两种地图的接口:

var googleMap = {
    show: function () {
        console.log('开始渲染谷歌地图');
    }
};
var bdMap = {
    show: function () {
        console.log('开始渲染百度地图');
    }
};
var renderMap = function (type) {
    if (type === 'google') {
        googleMap.show();
    } else if (type === 'bd') {
        dbMap.show();
    }
};
renderMap('google'); // 输出:开始渲染谷歌地图
renderMap( 'baidu' ); // 输出:开始渲染百度地图

  可以看到,虽然 renderMap 函数目前保持了一定的弹性,但这种弹性是很脆弱的,一旦需要替换成其他的地图接口,那无疑必须得改动 renderMap 函数,继续往里面堆砌条件分支语句。我们还是先把程序中相同的部分抽象出来,那就是显示某个地图:

var renderMap = function( map ){
    if ( map.show instanceof Function ){
        map.show();
  } }; renderMap( googleMap ); // 输出:开始渲染谷歌地图 renderMap( bdMap ); // 输出:开始渲染百度地图

  现在来找找这段代码中的多态性。当我们向两种地图对象分别发出“展示地 图”的消息时,会分别调用它们的 show 方法,就会产生各自不同的执行结果。对象的多态性提示我们,“做什么”和“怎么去做”是可以分开的,即使以后增加了其他地图,renderMap 函数仍 然不需要做任何改变,如下所示:

var sosoMap = {
    show: function(){
        console.log( '开始渲染搜搜地图' );
    }
}
renderMap( sosoMap ); // 输出:开始渲染搜搜地图

 

上一篇:联合类型和类型保护


下一篇:ex40模块类和对象