類別基礎(Class-based)與原型基礎(Prototype-based)為物件導向的不同風格。前者在設計時先強調物件的種類畫分,之後根據畫分的類別,建立具相同行為與結構的實例(Instance);後者先著重物件個體的行為描述,日後隨著程式演進再來擔心分類等問題。

類別規範了實例的行為與結構
在描述問題中重複出現的物件行為時,類別基礎是將觀察的物件行為予以分類,在類別中定義每個實例擁有的行為與結構,但實例允許各自的狀態。

在類別基礎的作法下,要規範類別的使用者較為容易。由於使用者建立的實例都是基於類別規範,實例間唯一允許的差異性只有狀態。在這樣的模型中,程式的穩定性較高,因為實例不會有其他行為或結構的可能性;程式的安全性也較高,除非修改類別定義,否則不用擔心使用者對某物件作了修改,從而影響另一使用者或甚至整個應用程式,也因此類別基礎的作法,適用於對可預測性要求較高的工程環境。

由於類別的限制,實例除了狀態外不允許有其他差異性,類別一旦定義與發布,想更新類別的行為與結構就不是件容易的事,畢竟這意謂著引用舊有類別的使用者,應用程式將受影響而需要修改。類別基礎的支持者往往注重程式開發前的分析,在實際撰寫程式前,得花費許多心力來探查需求。

原型基礎下的物件是自主學習個體

在描述問題中重複行為時,原型基礎的作法是先實作物件行為,必要時再針對一組行為實作過程重複的物件予以分類,目的在管理「指導物件能力的過程」,經由同一訓練過程的物件仍可透過其他管道,擁有個別的能力。

類別基礎下的物件有類別與實例兩種,實例的能力總是由類別規範,不允許物件擁有額外習得的技能,狀態是物件唯一可擁有的差異性;原型基礎下只有一種原始物件,物件的能力都是透過學習而來,具體來說就是對物件新增特性或行為,每個物件的能力與狀態都是獨一無二。

舉例來說,JavaScript是支援原型基礎的語言,如果需要一個物件擁有某些特性或行為,就必須對原始物件加以指導:

var b = {}; b.radius = 3;
b.volumn = function() { return 3.14 * Math.pow(this.radius , 3) * 4 / 3; };

由於指導物件能力的流程出現重複,因此可以適當地將重複流程予以封裝。例如若有b1、b2等物件同樣要透過以上流程進行指導,則可以如下,避免撰寫重複:

function Ball(b, r) {
b.radius = r; b.volumn = function() { …略 }; return b;
}
var b1 = Ball({}, 3); var b2 = Ball({}, 6);

經由共同流程的指導,已擁有一定能力的物件仍可以進一步個別指導。例如:

fb1.color = 'red'; b2.weight = 20;

原型基礎下的物件都是獨立個體,沒有類別來規範行為與結構,雖然像JavaScript這樣的語言,可使用類似類別基礎的語法來建構物件,然而實際上仍是指導物件能力的過程:

function Ball(r) { // 看似定義類別,其實是指導原始物件的流程
this.radius = r; // this會參考至傳入的原始物件
this.volumn = function() { …略 };
}
var b = new Ball(3); // 看似建構類別實例,其實是傳入原始物件

由於先描述物件的行為,原型基礎的作法極具彈性,可先就觀察到的行為進行實作,需求發生變化時也易於隨時增減物件行為,即使事先對物件的學習流程予以封裝並發布,後續使用者在不滿意原有實作的情況下,也可以根據需求重新指導物件的行為或結構。例如:

b.volumn = function() { return 3.1415 * Math.pow(this.radius , 3) * 4 / 3; };

然而這也是類別基礎的支持者不安之處:沒有內建機制可對物件行為與結構加以規範。在原型基礎下,使用者隨時可以修改物件,降低了程式的穩定性,既然可以對任何物件進行修改,通常也表示可以對程式核心物件進行修改,如此一來,引用核心物件的整體系統行為也會受到影響,進而引發安全性的問題。

原型模仿類別是一種慣例約束方式

類別基礎與原型基礎對物件能力規範的作法有著極大差異,習慣類別基礎的開發者面對原型基礎語言,往往急於尋求類別基礎語言中規範物件的類似方式,而未認清原型基礎的出發點在於支持物件個體化,因此對原型基礎語言產生誤解與誤用。例如直接將上述JavaScript範例中,Ball函式定義與new關鍵字的用法,看作是類別基礎語言中等效的類別定義與new建構語法;另一種常見誤用,就是在未瞭解原型基礎中物件學習能力本質的情況下,就嘗試以某種方式實作出類別基礎下繼承的概念,或沒有去理解程式實際需求,就直接引用套用某種模仿類別基礎的框架。

類似以上的錯誤認知,非但無法發揮原本使用類別基礎語言的經驗,也無法發揮原型基礎語言的真正彈性。原型基礎去掉了類別的約束,目的在換取物件學習能力上的彈性,然而對某些經驗不足的開發者而言,過大的彈性反而是一種危險,因而需要某些機制來約束。模仿類別封裝與繼承概念,其實是一種慣例約束方式,原型基礎要模仿類別基礎的方式很多元,該採用哪種模仿方式,正意謂著必須思考程式中需要哪一種慣例約束。這也正是原型基礎帶給來的彈性之一:可以根據需求採用不同慣例約束,而非語法層面上直接限制物件能力。

另一個約束物件個體性的方向是採用包裹器。舉例來說,JavaScript的Prototype程式庫採用對核心程式庫增加或修改行為的方式,並以模擬類別基礎的方式來約束開發者的使用模型,好處是類別基礎的開發者在運用時較為熟悉,壞處是更動核心程式庫並非最佳實務作法。jQuery採用包裹器的方式,使用包裹器收集要操作的物件,開發者對包裹器操作,包裏器則對收集的物件改變狀態,好處是不更動核心程式庫,符合最佳實務,然而開發者必須額外學習包裹器的使用架構。

注重工程管理或尊重個體差異的決擇
類別基礎與原型基礎對物件的能力管理方式不同,類別基礎偏重工程性,使用類別規範所有物件行為,在重維護的場合較具優勢,但語法彈性相較上自然顯得不足。原型基礎偏重物件個體性,可以輕易賦予物件不同的特性,有利於快速堆砌功能,但容易造成維護上的混亂,在應用於工程性偏重的場合,往往得採取某種慣例來約束物件行為,像是上述採用某種方式模擬類別或使用包裹器,避免開發者直接修改物件的行為與結構。

 

專欄作者

熱門新聞

Advertisement