设计模式——多态

lxf2023-05-05 11:08:01

【前言】

当我们谈论JavaScript设计模式时,多态是一个非常重要的概念。在面向对象编程中,多态是指同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果。换句话说,给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈。

【正文】

举例说明

主人家里养了两只动物,分别是一只鸭和一只鸡,当主人向它们发出“叫”的命令时,鸭会“嘎嘎嘎”地叫,而鸡会“咯咯咯”地叫。这两只动物都会以自己的方式来发出叫声。它们同样“都是动物,并且可以发出叫声”,但根据主人的指令(同一操作),它们会各自发出不同的叫声(不同的反馈)。

下面我们用代码来描述上述需求。

const makeSound = function( animal ){
    if ( animal instanceof Duck ){
      console.log( ’嘎嘎嘎’ );
    }else if ( animal instanceof Chicken ){
      console.log( ’咯咯咯’ );
    }
}

const Duck = function(){};
const Chicken = function(){};
makeSound( new Duck() );      // 嘎嘎嘎
makeSound( new Chicken() );   // 咯咯咯

这段代码确实体现了“多态性”,当我们分别向鸭和鸡发出“叫唤”的消息时,它们根据此消息作出了各自不同的反应。

但这样的“多态性”是无法令人满意的,如果后来又增加了一只动物,比如狗,显然狗的叫声是“汪汪汪”如果这时候主人家又来一条狗,我们需要怎么改写上述代码呢?

const makeSound = function( animal ){
    if ( animal instanceof Duck ){
      console.log( ’嘎嘎嘎’ );
    }else if ( animal instanceof Chicken ){
      console.log( ’咯咯咯’ );
    }
    //增加的代码
    else if ( animal instanceof Dog ){
      console.log( ’汪汪汪’ );
    }
}

const Duck = function(){};
const Chicken = function(){};
const Dog = function(){};
makeSound( new Duck() );      // 嘎嘎嘎
makeSound( new Chicken() );   // 咯咯咯
makeSound( new Dog() );   // 汪汪汪

这时候我们需要去修改makeSound函数的代码,而且增加一次判断。

修改代码总是危险的,修改的地方越多,程序出错的可能性就越大,而且当动物的种类越来越多时,makeSound有可能变成一个巨大的函数。

下面我们将改写这个危险的代码

多态背后的思想是将“做什么”和“谁去做以及怎样去做”分离开来,也就是将“不变的事物”与“可能改变的事物”分离开来。

在这个故事中,动物都会叫,这是不变的,但是不同类型的动物具体怎么叫是可变的。把不变的部分隔离出来,把可变的部分封装起来。

const makeSound = function( animal ){
    //不可变
    animal.sound();
};
const Duck = function(){}
Duck.prototype.sound = function(){
    //可变
    console.log( ’嘎嘎嘎’ );
};

const Chicken = function(){}
Chicken.prototype.sound = function(){
    //可变
    console.log( ’咯咯咯’ );
};

makeSound( new Duck() );        //嘎嘎嘎
makeSound( new Chicken() );     //咯咯咯

//增加狗
const Dog=function(){}

Dog.prototype.sound=function(){
    //可变
    console.log(’汪汪汪’);
};

makeSound(newDog());        //汪汪汪

ts版本

interface Animal {
  sound: () => void;
}

const makeSound = (animal: Animal): void => {
  animal.sound();
};

class Duck implements Animal {
  sound(): void {
    console.log('嘎嘎嘎');
  }
}

class Chicken implements Animal {
  sound(): void {
    console.log('咯咯咯');
  }
}

makeSound(new Duck());     //嘎嘎嘎
makeSound(new Chicken());  //咯咯咯

//增加狗
class Dog implements Animal {
  sound(): void {
    console.log('汪汪汪');
  }
}

我不关心你到底是什么动物,只要你有sound方法就行。

修改后的代码是我们的程序更利于扩展,程序看起来是可生长的,也是符合开放—封闭原则的,相对于修改代码来说,仅仅增加代码就能完成同样的功能,这显然优雅和安全得多。

其实在我们平时开发过程中上述的需求其实很多,以下是一段实战的代码。

实战代码

interface Map {
  show: () => void;
}

class GoogleMap implements Map {
  show(): void {
    console.log('开始渲染谷歌地图');
  }
}

class BaiduMap implements Map {
  show(): void {
    console.log('开始渲染百度地图');
  }
}

// 不利于扩展
// const renderMap = (type: 'goole'|'baidu'):void => {
//   if(type === 'goole){
//      GoogleMap.show()
//   }else if( type === "baidu"){
//     BaiduMap.show()
//   }
// }
// renderMap('goole');  // 输出:开始渲染谷歌地图
// renderMap('baidu');   // 输出:开始渲染百度地图


//利于扩展
const renderMap = (map: Map): void => {
  if (map.show instanceof Function) {
    map.show();
  }
};

const googleMap = new GoogleMap();
const baiduMap = new BaiduMap();

renderMap(googleMap);  // 输出:开始渲染谷歌地图
renderMap(baiduMap);   // 输出:开始渲染百度地图

总结

  1. 在我们日常开发中,我们要多思考变与不变,抽象出不变的部分。
  2. 有一个名词很有意思,叫鸭子类型。解释就是,如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。多态思想其实和这个类型很像,我不管你是什么动物,只要你会叫,我不管你是什么地图,只要你有渲染方法。

参考资料

[1] JavaScript设计模式和开发实践