Loading AI tools
来自维基百科,自由的百科全书
基於原型編程(英語:prototype-based programming)或稱為基於原型的編程、原型編程,是物件導向程式設計的一種風格和方式。在原型編程中,行為重用(在類別為基的語言通常稱為繼承),是通過複製已經存在的原型對象的過程實現的。這個模型一般被認為是無類的、面向原型、或者是基於實例的編程。
原型編程最初的(也是最經典的)例子是程式語言Self,它是由David Ungar和Randall Smith開發的。但是無類編程方式最近變得越來越受歡迎,並且被JavaScript、Cecil、NewtonScript、Io、REBOL,還有一些其他的程式語言所採納。
在類別為基編程當中,對象總共有兩種類型:類和介面。類包含儲存資料的結構和操縱資料的行為,結構是用資料欄位描述的,而行為是通過方法定義的。介面是不包含欄位的抽象類型,通常定義類必須實現的行為,介面不能實例化而必須被實現。所有的類通過提供結構和行為來實現一個介面。類可以從現存的類繼承而來,從而建立一種類層級。
原型編程的主張者經常爭論說,類別為基的語言提倡使用一個關注分類和類之間關係的開發模型。與此相對,原型編程看起來提倡,程式設計師關注一系列對象實例的行為,而之後才關心如何將這些對象劃分到最近的使用方式相似的原型對象,而不是分成類。因為如此,很多基於原型的系統提倡執行時原型的修改,而只有極少數類別為基的物件導向系統(比如第一個動態物件導向的系統Smalltalk),允許類在程式執行時被修改。
考慮到絕大多數基於原型的系統,是基於解釋型的和動態型別程式語言,這裡要重點指出的是,靜態型別語言實現基於原型從技術上是可行的。用基於原型編程描述的Omega語言[1],就是這樣系統的一個例子。儘管根據Omega網站所述,Omega也不是完全的靜態,但是可能的時候,它的編譯器有時會使用靜態繫結來改行程序的效率。
在類別為基的語言中,一個新的實例通過類構造器和給構造器的可選的參數來構造。在基於原型的語言中,沒有顯式的類,對象直接通過一個原型屬性從其他對象進行繼承,這個原型屬性,在JavaScript中叫做prototype
,在Io中叫做proto
。在基於原型的系統中,構造對象有兩種方法,通過複製(cloning)已有的對象,或者通過擴充空(nihilo)對象建立,因為大多數系統提供了不同的複製方法,擴充空對象的方式並不顯著[2]。
提供擴充空對象建立的系統允許對象從空白中建立,而無需從已有的原型中複製。這樣的系統提供特殊的文法,用以指定新對象的行為和屬性,無須參考已存在的對象。在很多原型語言中,通常有一個Object原型,其中有普遍需要的方法。它被用作所有其它對象的最終原型。擴充空對象建立可以保證新對象不會被頂級對象的命名空間污染。例如在JavaScript中,可以利用null
原型來做到,比如Object.create(null)
。
複製指一個新對象通過複製一個已經存在的對象(就是他的原型)來構造自己的過程。於是新的對象擁有原來對象的所有屬性,從這一點出發新對象的屬性可以被修改。在某些系統中,子對象持有一個到它原型的直接連結(經由授權或類似方式)。並且原型的改變同樣會導致它的副本的變化。其他系統中,如類Forth的程式語言Kevo,在此情況下不傳播原型的改變,而遵循一個更加連續的模型,其中被複製的對象改變不會通過他的副本傳播[3]。
// JavaScript中真实的原型继承样式的例子。
// 使用文字对象记号{}建立的“无中生有”对象。
var foo = {name: "foo", one: 1, two: 2};
// 另一个“无中生有”对象。
var bar = {three: 3};
// Gecko和Webkit JavaScript引擎可以直接的操纵内部的原型链接。
// 为了简单起见,我们假装下面几行代码可以工作而不考虑使用的引擎:
bar.__proto__ = foo; // foo现在是bar的原型。
// 如果我们尝试从bar访问foo的属性,从此以后会成功。
bar.one // 解析为1。
// 子对象的属性也是可访问的。
bar.three // 解析为3。
// 自身的属性遮蔽原型属性。
bar.name = "bar";
foo.name; // 无影响,解析为"foo"。
bar.name; // 解析为"bar"。
下面是個在 JavaScript 1.8.5 以上版本的例子(參見ECMAScript 5相容性表格[4])
var foo = {one: 1, two: 2};
// 等价于上例的bar.[[ prototype ]] = foo
var bar = Object.create( foo );
bar.three = 3;
bar.one; // 1
bar.two; // 2
bar.three; // 3
在使用委託的基於原型的語言中,語言執行時能夠分派正確的方法或找到正確的資料,只需遵循一系列委託指標(從對象到其原型)直到找到匹配項。在對象之間建立這種行為共享所需的只是委託指標。與類別為基的物件導向語言中的類和實例之間的關係不同,原型與其分支之間的關係不要求子對象在此連結之外與原型具有主記憶體或結構相似性。因此,子對象可以隨著時間的推移繼續被修改和修正,而無需像在類別為基的系統中那樣重新安排其相關原型的結構。要注意,同樣重要的是,不僅可以添加或更改資料,還可以添加或更改方法。出於這個原因,一些基於原型的語言將資料和方法都稱為「槽」(slot)或「成員」。
在串接原型(Kevo 程式語言實現的方法)中,沒有可見的指標或連結指向克隆對象的最初原型。原型(父)對象被複製而不是連結到,並且沒有委託。因此,對原型的更改不會反映在克隆對象中[5]。
這種安排下的主要概念差異是對原型對象所做的更改不會自動傳播到克隆。這可能被視為優點或缺點(然而,Kevo 確實提供了額外的原語,用於基於對象的相似性——所謂的家族相似性或克隆家族機制——而不是像委託模型中典型的那樣通過分類起源發布更改)。有時還聲稱基於委託的原型設計還有一個缺點,即對子對象的更改可能會影響父對象的後續操作。然而,這個問題不是基於委託的模型所原生的,也不存在於基於委託的語言(如 JavaScript)中,它確保對子對象的更改總是記錄在子對象本身中,而不會記錄在父對象中(即子對象的value 會影響父級的值,而不是更改父級的值)。
這樣做的好處包括,對象的作者可以修改這份副本,而無須擔心對此父類別的其他子類產生副作用。進一步的優點,是尋找屬性運算的消耗同授權相比大大降低了,授權尋找必須遍歷整個委託鏈才能判定不存在。
串接的壞處包括傳播變化到整個系統的難度;如果一個變化作用到某個原型,它不會立即或者自動的對它的所有副本生效。然而Kevo提供了額外的在對象系統中傳播變化的方式。這種方式是基於他們的相似性(所謂的family相似)[5],而非像委託模型具有代表性的那樣源自分類學。
另外一個壞處是在這個模型的大多數自然的實現下,每一個副本上都有額外的主記憶體被浪費掉了(相對委託模型而言),因為副本和原型之間有相同的部分存在。然而,在共享的實現和後台資料中提供串接行為的編程編程是可行的。這種做法為Kevo所遵從[6]。
那些經常批評基於原型系統而支援類別為基的對象模型的人,通常有類似靜態型別系統相對於動態型別系統的擔心。通常這些擔心是:正確性、安全性、可預測性以及效率。
在前三點上,類可以看作和類型等效(多數靜態語言遵守此規則),而且提供保證他們實例的契約,而對這些實例的使用者保證特定場景中的行為。
在最後一點上,效率,類的聲明簡化了編譯器的組織,允許開發高效的方法以及實例變數尋找。對Self語言來說,大多開發時間都消耗在開發、編譯以及解釋技術,用以改進基於原型的系統相對於類別為基的系統的效能。舉例來說Lisaac產生的代碼速度幾乎跟C一樣快。測試是由MPEG-2編碼器的Lisaac版本得出的,它由一個C語言版本複製而來。測試顯示,Lisaac版本比C版本慢1.9%,但代碼行數少了37%。然而C語言並非物件導向語言,而是一個程序式語言。Lisaac跟C++版本相比可能更說明問題。
最普遍的對基於原型的語言的批評,來自不喜歡它的軟體開發者社群,僅管JavaScript有著人氣和市場。對基於原型系統的了解程度,似乎因為JavaScript框架的廣泛應用,以及JavaScript針對web 2.0的複雜應用而改變[7]。很可能由於這些原因,在ECMAScript標準的第四版開始,尋求使JavaScript提供類別為基的構造,且ECMAScript第六版,提供「類」作為原有的原型架構之上的語法糖,提供建構物件與處理繼承時的另一種語法[8]。
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.