在前一篇中我們介紹了基礎的 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 程式能更加容易。