生活中的多态

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

什么是多态

动态类型语言语言
多态是指:同一动作/行为作用于不同的对象上面,可以产生不同的解释和不同的执行结 果。换句话说,给不同的对象发送同一个命令的时候,这些对象会分别给出不同的 反馈。
动态类型语言 ,它强调对是has-a,它关注的是对象的行为,而不是对象本身,只要对象具有某个方法就可以了。

静态类型语言:
多态是指,子类可以替换父类,在代码运行过程中,实际调用子类的方法实现。
静态类型语言,它强调的is-a,也就是它关注的是类本身,一个函数要想接受不同的类作为参数,必须对子类进行向上转型。

实现多态的三种方式

继承+方法重写

  1. // 这是一段TypeScript代码
  2. abstract class Animal {
  3. abstract sound(): void;
  4. }
  5. class Duck extends Animal {
  6. sound(){
  7. console.log('嘎嘎嘎')
  8. }
  9. }
  10. class Chicken extends Animal {
  11. sound(){
  12. console.log('咯咯咯')
  13. }
  14. }
  15. const animalSound = (animal: Animal) => {
  16. animal.sound()
  17. }
  18. animalSound(new Duck())
  19. animalSound(new Chicken())

DuckChicken 都继承 Animal,让 animalSound 接受 Animal 类型的参数而不是具体的 Duck 类型,或者 Chicken 类型即 向上转型

接口类

  1. interface Animal {
  2. sound(): void;
  3. }
  4. class Duck implements Animal {
  5. sound() {
  6. console.log('嘎嘎嘎');
  7. }
  8. }
  9. class Chicken implements Animal {
  10. sound() {
  11. console.log('咯咯咯');
  12. }
  13. }
  14. const animalSound = (animal: Animal) => {
  15. animal.sound();
  16. };
  17. animalSound(new Duck());
  18. animalSound(new Chicken());

DuckChicken 实现同一个接口 Animal

duck-typing

  1. // 这是一段JavaScript代码
  2. class Duck {
  3. sound() {
  4. console.log('嘎嘎嘎');
  5. }
  6. }
  7. class Chicken {
  8. sound() {
  9. console.log('咯咯咯');
  10. }
  11. }
  12. const animalSound = (animal) => {
  13. animal.sound();
  14. };
  15. animalSound(new Duck());
  16. animalSound(new Chicken());

DuckChicken 两个类没有任何关系,既不是继承关系,也不是接口和实现的关系,但是只要它们都有定义了 sound() 方法,就可以被传递到 animalSound()方法中,在实际运行的时候,执行对应的 sound() 方法。

也就是说,只要两个类具有相同的方法,就可以实现多态,并不要求两个类之间有任何关系,这就是所谓的 duck-typing,是一些动态语言所特有的语法机制。而像 Java 这样的静态语言,通过继承实现多态特性,必须要求两个类之间有继承关系,通过接口实现多态特性,类必须实现对应的接口。

多态的意义

对于上面的代码,假如我们不使用多态来实现:

  1. // 这是一段JavaScript代码
  2. class Duck {}
  3. class Chicken {}
  4. const animalSound = (animal) => {
  5. if(animal instance of Duck){
  6. console.log('嘎嘎嘎');
  7. }else if(animal instance of Chicken){
  8. console.log('咯咯咯');
  9. }
  10. };
  11. animalSound(new Duck());
  12. animalSound(new Chicken());

如果后来又增加了一只动 物,比如狗,显然狗的叫声是“汪汪汪”,此时我们必须得改动 animalSound 函数,才能让狗也发出 叫声:

  1. // 这是一段JavaScript代码
  2. class Duck {}
  3. class Chicken {}
  4. class Dog {}
  5. const animalSound = (animal) => {
  6. if(animal instance of Duck){
  7. console.log('嘎嘎嘎');
  8. }else if(animal instance of Chicken){
  9. console.log('咯咯咯');
  10. }else if(animal instance of Dog){
  11. console.log('汪汪汪');
  12. }
  13. };
  14. animalSound(new Duck());
  15. animalSound(new Chicken());

修改代码总是危险的,修改的地方越多,程序出错的可能性就越大,也违反了开闭原则,而且当动物的种类越 来越多时,animalSound 有可能变成一个巨大的函数,这又违反了单一指责原则

  1. // 这是一段JavaScript代码
  2. class Duck {
  3. sound() {
  4. console.log('嘎嘎嘎');
  5. }
  6. }
  7. class Chicken {
  8. sound() {
  9. console.log('咯咯咯');
  10. }
  11. }
  12. class Dog {
  13. sound() {
  14. console.log('汪汪汪');
  15. }
  16. }
  17. const animalSound = (animal) => {
  18. animal.sound();
  19. };
  20. animalSound(new Duck());
  21. animalSound(new Chicken());

而如果我们使用多态的方式编写代码的时候,我们只需要让 Dog 实现 sound 方法就可以了,完全不需要更改 animalSound 函数,这显然提高了代码的可扩展性

多态背后的思想是将不变的和变化的分开,在这个例子中就是,动物都会叫是不变的,不同的动物的叫声不同是变化的。

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

参考文献
https://time.geekbang.org/column/article/161114
《Javascript设计模式与开发实践》1.2 多态