在前一篇中我們介紹了基礎的 JavaScript 繼承實作法,透過 Object.prototype 我們可以自由決定物件要繼承自哪個物件,也可以擴充物件目前現有的屬性和方法 (和 C# 的 Extension Method 有異曲同工之妙),在本篇中,我們要來介紹物件導向的另一個特性:多型 (Polymorphism)。
在前一篇中我們介紹了基礎的 JavaScript 繼承實作法,透過 Object.prototype 我們可以自由決定物件要繼承自哪個物件,也可以擴充物件目前現有的屬性和方法 (和 C# 的 Extension Method 有異曲同工之妙),在本篇中,我們要來介紹物件導向的另一個特性:多型 (Polymorphism)。
多型的定義是,相同的行為 (behavior),在不同的物件上會有不同的反應。最常被舉的例子就是動物或車子了,例如我有一個 Car 物件,裡面有一個 getName() 方法,而我們定義了 HondaCRV 和 ToyotaWish 兩個物件來繼承它,其完整定義為:
1: function Car() {
   2:   
	3: this.name = "BASE";
   4:   
	5: this.getName = function () { return this.name; };
6: this.drive = function () { document.write("Drive BASE <br />"); };
   7:   
	
   8:  }
	
   9:   
	10: function HondaCRV() {
11: HondaCRV.prototype.name = "HONDA CRV";
  12:  }
	
  13:   
	14: HondaCRV.prototype = new Car();
  15:   
	16: function ToyotaWish() {
17: ToyotaWish.prototype.name = "TOYOTA Wish";
  18:  }
	
  19:   
	20: ToyotaWish.prototype = new Car();
然後在主程式這樣呼叫:
1: function init() {
   2:   
	3: document.write((new Car()).getName() + "<br />");
4: document.write((new HondaCRV()).getName() + "<br />");
5: document.write((new ToyotaWish()).getName() + "<br />");
   6:   
	
   7:  }
可得到這樣的結果:
這個範例程式就是典型的多型運用,雖然它共用了父類別的變數,不過我們還可以再進一步做多型的能力。在 Car 中有一個方法 drive(),現在我們要在 HondaCRV 和 ToyotaWish 中覆寫它,程式如下:
1: function Car() {
   2:   
	3: this.name = "BASE";
   4:   
	5: this.getName = function () { return this.name; };
6: this.drive = function () { document.write("Drive BASE <br />"); };
   7:   
	
   8:  }
	
   9:   
	10: function HondaCRV() {
11: HondaCRV.prototype.name = "HONDA CRV";
12: HondaCRV.prototype.drive = function () {
13: document.write("Drive HONDA CRV now. <br />");
  14:      };
	
  15:  }
	
  16:   
	17: HondaCRV.prototype = new Car();
  18:   
	19: function ToyotaWish() {
20: ToyotaWish.prototype.name = "TOYOTA Wish";
21: ToyotaWish.prototype.drive = function () {
22: document.write("Drive TOYOTA Wish now. <br />");
  23:      };
	
  24:  }
	
  25:   
	26: ToyotaWish.prototype = new Car();
請注意,我們現在使用了 Object.prototype.[method] 的方式來覆寫父物件的方法,以執行物件自己的動作。然後修改主程式:
1: function init() {
   2:   
	3: document.write((new Car()).getName() + "<br />");
4: document.write((new HondaCRV()).getName() + "<br />");
5: document.write((new ToyotaWish()).getName() + "<br />");
   6:   
	7: (new Car()).drive();
8: (new HondaCRV()).drive();
9: (new ToyotaWish()).drive();
  10:   
	
  11:  }
執行它,我們可以得到下面的結果:
到了這裡,我想你應該了解如何使用 JavaScript 實作多型的功能了,接著我們就要來做和多型有高度相關的功能:介面 (interface)。
有寫過 C#/Java/VB 這種物件導向語言程式的人應該都知道,介面是一種合約 (contract),它具有很強的強制性,只要是有參考介面但未實作的話會被擲回編譯錯誤,所以不用擔心介面沒有被實作,然而 JavaScript 是一種型別鬆散的直譯式語言,沒辦法強制執行這種檢查,所以這部份我們得自己做,不過 JavaScript 有些方便的輔助物件,可以幫我們解決一些事情。
例如,我們訂了一個 IRateCalculator 介面,裡面有一個 getAmount() 方法:
1: function IRateCalculator() {
2: // contract method.
3: this.getAmount = function (amount) { throw "ERROR_INTERFACE_METHOD_MUST_BE_IMPLEMENTED"; };
   4:  }
然後我們定義了兩個物件 SavingCalculator 與 LoanCalculator,皆實作 IRateCalculator 介面,定義自己的 getAmount() 方法:
1: SavingCalculator.prototype = new IRateCalculator();
   2:   
	3: function SavingCalculator(amount) {
   4:   
	5: this.amount = amount;
   6:   
	7: SavingCalculator.prototype.getAmount = function (amount) {
8: return amount * 1.01; // 1%
   9:      };
	
  10:   
	
  11:  }
	
  12:   
	13: LoanCalculator.prototype = new IRateCalculator();
  14:   
	15: function LoanCalculator(amount) {
  16:   
	17: this.amount = amount;
  18:   
	19: LoanCalculator.prototype.getAmount = function (amount) {
20: return amount * 1.20; // 20%
  21:      };
	
  22:   
	
  23:  }
在主程式中我們要使用 SavingCalculator 和 LoanCalculator 計算十萬元的本利和:
1: function init() {
   2:   
	3: var saving = new SavingCalculator();
4: var loan = new LoanCalculator();
   5:   
	6: // check interface.
   7:      console.log(IRateCalculator.prototype);
	
   8:      console.log(SavingCalculator.prototype);
	9: console.log(typeof SavingCalculator.prototype.getAmount);
  10:   
	11: if (IRateCalculator.prototype.isPrototypeOf(saving))
12: document.write("Saving's Rate Amount of 100000 is: " + saving.getAmount(100000) + "<br />");
13: else
14: document.write("Your code is not implement IRateCalculator interface.");
  15:   
	16: if (IRateCalculator.prototype.isPrototypeOf(loan))
17: document.write("Loan's Rate Amount of 100000 is: " + loan.getAmount(100000) + "<br />");
18: else
19: document.write("Your code is not implement IRateCalculator interface.");
  20:   
	
  21:  }
執行結果為:
看起來很平常吧,但其實問題有兩個:
	1. 主程式必須要確認物件有實作 IRateCalculator 介面。
	2. 主程式必須檢查物件確實實作了 IRateCalculator.getAmount() 方法。
針對第一個問題,我們可以利用 Object.prototype.isPrototypeOf() 方法來確認,它可以檢查介面是否被某物件實作,因此我們檢查了這件事,並決定是否要呼叫 getAmount() 方法。然而第二個問題仍無解,因為 Object.prototype 無法回傳更多的型別資訊,因此無法得到確實的型別比對依據,無法很確定 SavingCalculator 和 LoanCalculator 的 getAmount() 確實就是 IRateCalculator.getAmount() 方法,只能夠暫時信任物件實作的確實是介面所定義的方法。
善用多型,可以創造出很多不同的 JavaScript 物件應用,而且它也能做為 Design Pattern 的入口基石,讓編寫可高度重覆使用的 JavaScript 程式能更加容易。