前言

一直很难去真正理解JavaScript的多态到底是什么,从各种教程上对多态的解释都是:同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果

举例说明:移动这个功能,作用在汽车上,他能实现100公里每小时,如果作用在自行车上,只有20公里每小时,一个加油,一个用脚蹬,实现方式和结果完全不同。

但是这也没能解释js中的多态到底是什么?

如果从代码上去看的话,可以如下:

/** 汽车 */
function Car() {}

/** 自行车 */
function Bicycle() {}

/** 移动 */
function move(obj) {
  if (obj instanceof Car) {
    console.log("汽车移动每小时100公里");
  } else if (obj instanceof Bicycle) {
    console.log("自行车移动每小时20公里");
  }
}

const car = new Car();
const bicycle = new Bicycle();

move(car); // 汽车移动每小时100公里
move(bicycle); // 自行车移动每小时20公里

有点儿懵逼,这就多态了?

实际上这段代码确实体现了多态性,但是和最终的多态还有些距离,这个作为入门示例,让我们先有一个大概的了解。

多态起源

编程的语言可以大体的分为两类:静态类型语言、动态类型语言。

静态类型语言以Java为例,它的变量类型在编译前就确定了,这使得编译器可以在开发阶段就可以规避一些错误,并且还可以针对已知的类型做优化处理。

而动态类型语言,只有它在运行时被赋值后才可知变量的类型,而js就是动态类型语言。

由于动态类型语言的宽泛性,我们不会去检查对象的具体类型,而是去尝试调用对象的方法,哪怕它设计的时候并没有该方法。

这种认知都建立在鸭子类型的概念上。

鸭子类型(Duck Typing)是程序设计中的一个概念,特别是在动态类型语言中,它强调“如果它走起路来像鸭子、叫起来也是鸭子,那么它就可以被认为是鸭子”,即一个对象如果具有某些特定的方法和属性,那么它就可以被认为是某个类型,即使它在继承体系上并不真正属于这个类型

也就是说,我们不关心它具体的类型,只要它有我们需要的方法,我们就可以认为是正确的类型,是我们需要的对象。

这种设计使得我们在写代码时非常便利,但是在静态类型语言中,就没有那么方便了。

假设我们有一个需求,需要实现一个移动功能。

public class Car {
  public void move() {
    System.out.println("汽车移动每小时100公里");
  }
}

public class Bicycle {
  public void move() {
    System.out.println("自行车移动每小时20公里");
  }
}

public class Run {
  public void move( Car car ) {
    car.move();
  }
}

public class People {
  public static void main( String[] arguments ) {
    Run run = new Run();
    Car car = new Car();
    run.move(car);   // 汽车移动每小时100公里
  }
}

由于静态类型的限制,Run的move方法只能接收Car类型的对象,如果我要换成自行车,就会报错。

为了解决这个问题,静态类型语言在面向对象设计时,是可以向上转型的:当给一个类变量赋值时,这个变量的类型既可以是使用这个类的本身,也可以使用这个类的超类型。

也就是说,如果我们需要使用汽车移动,也需要使用自行车移动,那么可以向上转型为:我们需要一个交通工具移动。

这个交通工具在代码中可以是父类,抽象类,接口,当我们使用这个超类型时,就可以传入它的下级Car或者Bicycle,这种能让对象交换使用的行为,就是多态。

而超类型的常用手段可以分为:实现继承、接口继承。

这里以实现继承为例:

/** 交通工具 */
public abstract class Vehicle {
  abstract void move();  // 抽象方法
}


public class Car extends Vehicle {
  public void move() {
    System.out.println("汽车移动每小时100公里");
  }
}

public class Bicycle extends Vehicle {
  public void move() {
    System.out.println("自行车移动每小时20公里");
  }
}

public class Run {
  public void move( Vehicle vehicle ) {
    vehicle.move();
  }
}

public class People {
  public static void main( String[] arguments ) {
    Run run = new Run();
    Car car = new Car();
    Bicycle bicycle = new Bicycle();
     
    run.move(car);   // 汽车移动每小时100公里
    run.move(bicycle);  // 自行车移动每小时20公里

  }
}

从静态语言的例子中,我们再对比JavaScript,可以发现js对于这种多态的实现那真是天然优势,因为我们不需要明确对象类型。

/** 汽车 */
function Car() {}
Car.prototype.move = function () {
  console.log("汽车移动每小时100公里");
};

/** 自行车 */
function Bicycle() {}
Bicycle.prototype.move = function () {
  console.log("自行车移动每小时20公里");
};

function run(obj) {
  if (typeof obj.move === "function") {
    obj.move();
  }
}

const car = new Car();
const bicycle = new Bicycle();

run(car); // 汽车移动每小时100公里
run(bicycle); // 自行车移动每小时20公里

再仔细分析,可以发现“多态”的思想实际上是把“做什么”和“谁去做”分离开来,为了实现这一点,我们还需要消除类型之间的耦合关系,否则我们没法切换不同的对象。

换句话说,多态最根本的作用是将原来过程化的条件分支转化成了对象的多态性,从而消除这些条件分支语句。

总结

多态的应用在设计模式中非常广泛,比如:命令模式、组合模式、策略模式,而设计模式本身就是对封装、继承、多态、组合等技术的反复使用,从而提炼出可重复使用的面向对象设计技巧。

分类: JavaScript设计模式与开发实践 标签: javascript设计模式多态

评论

暂无评论数据

暂无评论数据

目录