2012/8/20

JavaScript 中的 Delegation

在物件導向理論中, 關於 Delegation (委託) 的概念, 有人說是由麻省理工學院 Media Lab 的首席研究員 Henry Lieberman 於 1986 年在 OOPLSA '86 Conference 中發表的一篇文章 ("Using prototypical objects to implement shared behavior in object-oriented systems") 裡面提出來之後, 才逐漸發揚光大。其實在這篇文章中, Lieberman 不只談到了委託, 他也談到了繼承。他同時提出了這兩種方法, 主要還是為了解決同一種問題 (亦即處理 "Shared Behavior" 的問題)。

Lieberman 很明確的指出 Inheritance 並不能取代 Delegation, 主要是因為這兩者的 approach 並不相同。我想, 關於繼承這方面的概念, 所有熟悉 OOP 的朋友們應該都很清楚了, 所以我就不再囉嗦。倒是在委託這方面, Lieberman 這篇文章的主軸就在 Prototyping 上面, 而他也認為藉由 Prototyping, 採用委託可以提升系統的彈性、減輕平行處理的問題、可在執行時期動態決定參數等等好處。

在這篇文章裡, 解說 Prototyping 的部份有點冗長。但是以下我要拿 JavaScript 來簡單地解說 Delegation 的作法, 因為不同於其它的 OOPS, 我們可以說 JavaScript 幾乎是完全採用 Lieberman 的概念來實作 Delegation 的。

JavaScript 語言中的 prototype

在繼續進行之前, 我們必須來復習一下 JavaScript 中提供的 prototype 物件。

其實 prototype 並不是什麼新東西, 早在 JavaScript 1.1 中就已經有了。但如果你今天是第一次認識它, 沒關係, 因為我認為至少有一半以上的程式設計師都跟它很不熟。

通常當我們在講到 OOP 時, 都不會把 JavaScript 算進去, 但是等你看完這一篇文章, 或許你會願意重新考慮一下。

為了營造神秘感, 我要在還沒有解釋 prototype 之前, 先讓你看看這一段程式:

function Chess() { };
Chess.prototype.move =
    function () {
        alert(this.chessName + " moved");
    };

看完程式, 我知道你一定會有幾大疑點:

  1. Chess 是一個 function, 為什麼一個 function 會有屬性?
  2. 這個 prototype 是什麼東西?
  3. this.chessName 未曾宣告就拿來用, 不怕讀到 undefined 嗎?

首先, JavaScript 雖然是個弱型別語言, 但它還是有型別的。除了各種原始型別 (數字、字串等), 另外有 object 和 function 兩種「複雜型別」。所以要記得, function 本身是一種型別, 而 prototype 則是 function 型別所內建的物件。

不過, 到底 prototype 是什麼東西? 如果你把上面 Lieberman 的那篇文章看完, 你就會知道 prototype 是什麼東西, 因為在 JavaScript 語言中, 大致上就是以那篇文章所描述的概念來定義 prototype 的。但如果要以最簡短的方式來描述它, 那麼, prototype 就是一種機制, 可以用來定義物件實體的自定屬性及方法。

這麼說, 恐怕還有些模糊; 我們還是回頭來看看程式好了。

在上面的程式中, 我們為 Chess.prototype 這個物件宣告了 move 這個方法, 而這個方法只做一件事, 也就是執行 alert(this.chessName + " moved") 這個動作。

是的, 我再說一次, Chess 是一個 function, 而 Chess.prototype 是一個物件, 而我為 Chess.prototype 這個物件宣告了 move 這個 function。

使用同樣的方法, 你可以為任何一個 function (例如 ABC()) 宣告一個叫做 ABC.prototype.XXX 的任何 function。

如果你看到這裡還是完全無法理解, 那麼或許你可以把這個 prototype 當作是 OOP 理論裡的 template, 或者 Class。不過, 這只是幫助你理解而已; 它其實不能稱為 Class, 因為真的不太一樣 (JavaScript 2.0 以後會有真正的 Class, 只不過目前似乎並沒有人在用 JavaScript 2.0)。但話說回來, 一些 JavaScript framework 確實是透過 prototype 來模擬 Class。

那麼, 程式裡的 this.chessName 又是什麼東西?

其實這裡的 chessName 不是重點, this 才是。

chessName 只是一個 run-time 才決定的變數名稱; 如果你 JavaScript 已經寫了一段時間, 那麼應該很能夠適應這種情況了。至於 this, 其實這個 this 並不是這個 function 的 this; 這個 this 是 client 的 this。這個概念講起來像在繞口令; 待會我會舉實例來說明。

Delegation 的運用

我在這裡要舉的例子, 坦白說差不多就是 Design Pattern 中 template method 的實作。但是和在其它 OOPS 的做法就是不太一樣。

以下我把完整的程式碼列出來:

function Chess() { };
Chess.prototype.move =
    function () {
        alert(this.chessName + " moved");
    };

function Soldier() {
    this.chessName = "Soldier";
};
Soldier.prototype = new Chess();

var soldier = new Soldier();
soldier.move();

在程式的下半段中, 我另外定義了一個叫做 Soldier 的 function, 而且在這裡定義了 this.chessName 的值。

然後我把 Soldier.prototype 指定給了 Chess。意思就是說把 Soldier 這個 function 的 prototype 指向 Chess 的 prototype; 若以其它 OOP 的術語來說, 就是指定了 Soldier 作為 Chess 的 Delegate。

最後, 當我執行 soldier.move() 時, 輸出的文字是 "Soldier moved"。

上面曾經提到, this 在這裡是重點; 如你所看見的, 雖然實際上 move 方法是在 Chess 中定義, 實際上這裡的 this 傳遞的是 client (亦即 soldier) 的 this 的值, 而不是 Chess 自己的值。這也是 prototype 物件的一個特色。透過這個特色, Delegation 才得以實作。

就像我在一開始就說的, 不管我們採用繼承或是委託, Lieberman 要解決的問題都是同一個: "Shared Behavior"。而在 JavaScript 中, 我們可以透過 prototype 做到 Delegation。在上述的例子中, 我們可以循相同的方法繼續擴充 prototype 中的方法, 讓其它 delegate function 可以共同運用。

許多 OOP 同時提供了繼承和委託 (例如 VB、C# 和 Java), 但是很可惜的, JavaScript 只提供了委託 (方法如上所述)。在 Lieberman 的文章裡, 他其實比較偏愛 Delegation (透過 Prototyping)。既然繼承和委託都可以解決問題, 所以 JavaScript 不提供繼承 (事實上, 前面已經說過, 它連 Class 都沒有) 而僅提供委託, 是有其道理的。

本質上, JavaScript 恐怕很難直接提供繼承, 除非等到 JavaScript 2.0 以後。因為在 JavaScript 中, 唯一和 Class 類似的就是 function; 但是 JavaScript 中的每個 function 都是天生的 Singleton -- 所以除了使用 Delegation, 程式設計師沒有其它太多的選擇。

在上述範例中, 我建立了一個 Soldier 函式 (兵/卒) 來代理 Chess 函式 (象棋), 這是 JavaScript 中的做法。如果使用 C# 或者 VB, 那麼我倒比較偏好採用繼承的方式, 亦即使用 Soldier 類別來繼承 Chess 類別。但是由於 JavaScript 沒有繼承, 我們只能採用委託。

接著, 如果程式再發展下去, 我們可以繼續再建立其它的函式, 像是將/帥、仕/士、象/相... 等等函式, 以及其它的 template method (從 Dsign Pattern 中偷個名詞來借用一下, 否則我也不知道如何稱呼它) 和其它欄位, 運用相同的做法, 直到程式完成為止。

在一些比較深入探討 JavaScript 的書籍中, 時常把 JavaScript 的 Delegation 直接說成 Inheritance (他們也直接把 function 稱之為 class)。但是若要嚴格定義的話, 委託和繼承根本不是同一件事情, 而 JavaScript 中的 function 跟許多人所熟知的類別更是天差地遠。許多人平常很喜歡咬文嚼字, 一定要清楚地區分說這個不是那個, 那個又不是哪個... 但是遇到 JavaScript, 突然又什麼都可以了。

對我個人而言, 如果有人一定要說 Delegation 跟 Inheritance 一樣啦, 或者說 function 跟 class 一樣啦, 那麼我即使心存保留態度, 也不想跟進去攪和。不過委託和繼承在其它 OOPS 中既然有明確的區隔, 我想, 除非你絕對不踫 C#/VB/Java 等等語言, 否則把這些名詞的定義搞清楚, 應該是沒有壞處的。

沒有留言:

張貼留言