天天看點

繼承與混合,略談系統的建構方式

這兩天在讀kissy的源代碼,從一開始我就對它的mix()函數充滿了敵意。因為無論從哪個角度來看,那都是一個極其低效的實作。不過深入了解這個架構之後,我對kissy中的新的系統建構的模型産生了興趣,而這種系統建構的方式,也正是由mix()所帶來的。

 

一、對象系統

  

我們先了解一下對象系統。在《JavaScript語言精髓與程式設計實踐》中談到過,面向對象系統有三種對象的繼承方式,即原型、類和元類。這三種方式都可以建構大型對象系統。在後續讨論之前,我們在名詞概念上做一些強調,所謂“對象系統”,是指由“一組對象構成的系統”,這些對象之間存在或不存在某種聯系,但通過一些規則組織起來。所謂“面向對象系統”,是指以上述“對象系統”對基礎延伸演化的系統,新系統滿足前對象系統的組織規則。

所謂對象系統的三個要素,即繼承、封裝與多态,即是上述組織規則的要件。孟岩同學從C/C++出發,從另一個側面談論對象系統,所持的觀點我相當認可,這包括所述的“對象範式的基本觀念中不包括繼承、封裝與多态”——這一觀點有其确切的背景與思考方法,值得一談。

我們在這裡要讨論的是“對象系統”,即,對象是如何組織起來的問題。在這個問題上,組織規則之一,就是“繼承”。JavaScript中基本的繼承模型是原型繼承,其特點是“新對象執行個體的特性,複制自一個原型對象執行個體”。Qomo以及其它的一些項目,通過語言擴充的方式,在JavaScript上添加了類繼承的模型,其特點是“對象建構自類,類是其父類的一個派生”,這裡的“派生”與“特性複制”有潛在的關系,即:子類的特性也複制自父類。正是因為“派生”其實是特性複制的一種形式,是以事實上Qomo中的類繼承,是通過原型繼承來實作的,因為原型繼承本質上也就是“特性複制”。

無論是原型繼承、類繼承還是這裡沒有進一步讨論的元類繼承,繼承的最終目的是建構一個“對象系統”,而不是“系統”。這一個小小的措辭上的差別,有着本質上的、深刻的意義,這也是我提及到孟岩的上一篇文章的原因。通常,由“繼承”入手了解的“對象系統”其實是靜态的,以至于我們面向對象系統開發的最後一步,仍然需要架構來驅動之。例如TApp.Run(),或者類似于new App(),等等。繼承所帶來的,主要仍然是指對象系統的組織性,而非其運作過程中的動态特性。于是我們通過更多類或其它對象系統,來将一個系統的動态特性靜态化,例如将對象之間的互動關系抽取出來,變成控制類。我們做這些事情的目的,僅僅是因為我們約定了對象系統的組織規則,要面向這個對象系統開發,也必然滿足(或契合)這一組織規則。組織規則限定了我們建構系統的方式——繼承、封裝與多态,這在一定程度上說是“對象系統建構”的一個方案,并非“系統建構”的方案。而孟岩在上文中讨論的,也正是“系統建構”的問題。是以孟岩提出兩點:

•程式是由對象組成的; 

•對象之間互相發送消息,協作完成任務。

其第一條,是對象系統的基本特性,是謂系統成員;第二條,是對象系統如何演進為系統的特性,是謂系統通訊。一個系統的限制,既包括其成員(以及成員的組織規則),也包括成員間的通訊。

二、用mix()來建構系統

舍棄“繼承”這種方式不談,系統建構還有其它的什麼方法嗎?

kissy提供了另外一種可能性,即mix(),混合。在kissy系統的核心部分,為一個系統提出了三個概念:

1、原子(meta),一個系統具有至少一個原子,原子是具有mix()能力的一個對象;

2、宿主(host),一個系統有一個依賴的宿主,表明系統的外部環境,系統隻是其宿主環境中的部分内容,可以由特定的名稱來差別于其它;

3、種子(seed),一個系統誕生自一個種子,種子描述系統上述的meta和host兩個方面的特性。

kissy約定,一個系統誕生自一個種子,該種子通過不停地mix()而成長,變成一個複雜的系統。由種子培育成為系統的整個環境,隻需要能夠了解mix與host,即可以基于seed來建構任意複雜的系統。

上述的邏輯在kissy.js中,描述得相當簡單:

[javascript] ​​view plain​​​​copy​​

  1. (function(host, S) {  
  2.     var meta = {  
  3.         mix: function(r, s, ov, wl) {  
  4.              ...  
  5.         }  
  6.     },  
  7.     // If KISSY is already defined, the existing KISSY object will not  
  8.     // be overwritten so that defined namespaces are preserved.  
  9.     seed = (host && host[S]) || {},  
  10.     // The host of runtime environment. specify by user's seed or <this>,  
  11.     // compatibled for  '<this> is null' in unknown engine.  
  12.     host = seed.__HOST || (seed.__HOST = host || {});  
  13.     // shortcut and meta for seed.  
  14.     S = host[S] = meta.mix(seed, meta, false);  
  15.     return S;  
  16. })(this, 'KISSY');  

這個系統初始化的時候,傳入host與host中的系統名S。對于kissy來說,host是目前的系統環境,這裡的this值,可以是javascript引擎的global,或浏覽器環境的window,或某個函數或對象閉包内的目前this。而'KISSY'值,表現kissy系統在環境中的名字。按照javascript的語言約定,我們可以通過host[S]來找到既已經存在的kissy系統。

按照此前的約定,一個mix建構的系統,必然有host和mix兩個性質,因為它最原始的種子(seed)就必然包括這兩種性質。是以,既然我們上面是通過host[S]來通路一個“既已存在的kissy系統”,則無論該kissy系統經過了何種程度的演化,必然會包括這兩種性質。

上面的建構過程嘗試尋找在host[S]中尋找這兩種性質,如果其中之一不存在,則嘗試初始化它。例如代碼:

  1. seed = (host && host[S]) || {},  

如果host[S]是存的,則假設它是一個seed,否則初始化為一個空的對象。接下來:

  1. host = seed.__HOST || (seed.__HOST = host || {})  

如果上述的種子seed有host屬性,則使用它既有的__HOST,如果沒有,則置為目前環境下的host,或一個空的對象。

現在我們看到的seed,必然已經具有host屬性。但是,它還“可能”缺少一個性質,即最最重要的mix()。mix()的作用其實很簡單,就是從對象B将屬性抄寫到對象A的一個方法。而這裡,之是以說是“可能”缺少,是因為如果seed是既有的mix系統,則他已經有mix()屬性;如果它是第三方系統,則可能沒有mix,或有一個不同的mix等等。下面的一行代碼嘗試用元語言的思想建構它,即:

  1. meta.mix(seed, meta, false);  // false值表明不覆寫  

元語言的特點是自描述的,meta.mix()可以向seed混入mix(),也可以使seed.mix()能混入其它系統或meta本身。總之在mix()的建構中,meta隻需要有mix這個方法,不需要更多,也不能更少。

上一行代碼的結果,是:如果seed沒有自已的mix()屬性,則向seed混入meta的原始的mix()。

現在,我們再看seed,必然已經具有了host和mix()屬性。它本身可能是一個空對象,也可能是一個龐大的既有系統,但無論如此,它具有了這兩個性質,就可以作為seed進一步的衍生。

在這一切之前,下面的代碼保證它位于HOST[S],并傳回這個系統:

  1. S = host[S] = meta.mix(seed, meta, false);  
  2. return S;  

三、mix()系統建構中的其它概念

kissy除了實作基本的mix系統之外,在core部分加入了其它的一些功能。包括除mix()之外的兩種混入方法:

 - augment,擴充。用mix方法,将另一些子系統s[i]的原型,混入目标子系統r的原型。

 - merge,合并。用mix方法,将另一些子系統s[i],混入目前子系統S。

基本上來說,augment是通過mix來對javascript的原型系統進行擴充的方法,或是在應用系統中,結合原型機制與混入機制來建構系統。而merge隻是mix方法的一個批量工具。

另外,考慮到面向對象系統中的繼承特性,kissy也實作了extend(派生)方法,以提供傳統的面向對象程式設計能力。

除了語言級别的概念之外,kissy也提供系統架構級别的一些建構能力。包括:

  - app,應用。與host[S]并列的,具有同等能力的其它應用,app('XXX', ...)可以在host['XXX']上組織應用。

  - namespace,命名空間。即可以組織出host[XXX].YYY.ZZZ這樣的,在不同子系統中的,不同命名空間下的系統。

最後,kissy在核心中也提供簡單的調試支援。

顯然的,基于mix的原則,任何一個第三方的系統可以通過混入kissy來修改上述的概念,例如覆寫extend()來實作自己的對象系統建構原則,或覆寫app()來實作自己的應用組織原則。第三方系統也可以将kissy混入自身,在保障自身特性的情況下,使用kissy,以及更大規模的kissy ui系統帶來的好處。

四、一點點提示

kISSY是什麼?

KISSY是一個開源的javascript項目,其主體是一個前端UI開發架構,即KissyUI。本文所述的kissy是僅指其核心部分的kissy.js中的語言與架構設計思想。KISSY項目的開源網站是:​​http://kissyteam.github.com/​​

kissy怎麼使用呢?

盡管在KissyUI向kissy核心化的過程中,我們提出了一些新的概念與架構模型,但事實上,我們并未改變KissyUI的任何使用慣例。從代碼上來看,kissy.js和lang.js以後的其它子產品,并沒有任何的變化,是以如果僅是将kissy當成一個UI系統來使用,你可以參考上面的開源網站,其中既有的KissyUI文檔是完全有效的,而且KissyUI本身也是一個優秀的、便捷的Web UI架構。但是,kissy系統在模向合并群組織上的能力大大增強了。例如說,我們可以開始想象下面這樣的代碼:

[xhtml] ​​view plain​​​​copy​​

  1. <!-- 先裝載jQuery -->  
  2. <script src="jQuery.js" mce_src="jQuery.js"></script>  
  3. <!-- 将jQuery系統映射到KISSY,作為初始的seed -->  
  4. <script type="text/javascript">  
  5.   window.KISSY = jQuery;  
  6. </script>  
  7. <!-- 裝載kissy,在jQuery上混入KISSY -->  
  8. <script src="kissy.js"></script>  
  9. document.writeln('你現在使用的是Kissy,還是jQuyer?答案請選Y,或者Y');  
  10. document.writeln('你現在能裝載Kissy UI,還是jQuery UI?答案請選Y,或者Y');  
  11. KISSY.merge(YUI, Dojo, Qomo).merge(Biby);  
  12. document.writeln("what's KISSY? select collapsar or black hole, pls

繼續閱讀