[JavaScript] JavaScript 物件導向設計 (3) : 多型與介面篇

在前一篇中我們介紹了基礎的 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:  }

 

可得到這樣的結果:

image

 

這個範例程式就是典型的多型運用,雖然它共用了父類別的變數,不過我們還可以再進一步做多型的能力。在 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:  }

 

執行它,我們可以得到下面的結果:

image

 

到了這裡,我想你應該了解如何使用 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:  }

 

執行結果為:

image

 

看起來很平常吧,但其實問題有兩個:

1. 主程式必須要確認物件有實作 IRateCalculator 介面。
2. 主程式必須檢查物件確實實作了 IRateCalculator.getAmount() 方法。

針對第一個問題,我們可以利用 Object.prototype.isPrototypeOf() 方法來確認,它可以檢查介面是否被某物件實作,因此我們檢查了這件事,並決定是否要呼叫 getAmount() 方法。然而第二個問題仍無解,因為 Object.prototype 無法回傳更多的型別資訊,因此無法得到確實的型別比對依據,無法很確定 SavingCalculator 和 LoanCalculator 的 getAmount() 確實就是 IRateCalculator.getAmount() 方法,只能夠暫時信任物件實作的確實是介面所定義的方法。

 

善用多型,可以創造出很多不同的 JavaScript 物件應用,而且它也能做為 Design Pattern 的入口基石,讓編寫可高度重覆使用的 JavaScript 程式能更加容易。