業務建模 之 閑話'閉包'與'原型繼承'

來源:互聯網
上載者:User

標籤:

【引言】在業務建模中,我們經常遇到這樣一種情況:“原型”對象負責實現業務的基本訴求(包括:有哪些屬性,有哪些函數以及它們之間的關係),以“原型”對象為基礎建立的“子物件”則實現一些個人化的業務特性,從而方便的實現業務擴充。最常見的搞法是:

1. 定義一個‘建構函式’,在其中實現屬性的初始化,例如:var Person = function( ){};    //函數體中可以進行一些變數的初始化。

2. 再設定該函數的prototype成員,例如:Person.prototype = { gotoSchool:function(){ console.log( ‘on foot‘ );} };                           //該對象字面量中定義一些方法

3. 用new來建立一個新對象,例如:var student = new Person();

4. 個人化新對象的部分行為:student.gotoSchool = function(){ console.log( ‘by bus‘ ); } ;

    >>根據new 和 原型鏈的特性,調用 student.gotoSchool();  將會輸出 by bus,而不是 on foot。

5. 同理,用new來建立一個teacher的對象,然後再設定它的gotoSchool的成員。

    var teacher = new Person();    teacher.gotoSchool =  function(){ console.log( ‘by car‘ ); } ;    teacher.gotoSchool() ;        //將會輸出 by car 

說明:本文中的代碼可以在Chrome瀏覽器的控制台中執行驗證。方法如下:按F12後單擊Console頁簽,開啟Chrome的控制台,可以看到console.log輸出的結果。

上面的方式能夠滿足我們的基本訴求,並且在之前的Web控制項自訂開發中,我們也是這麼做的。但是,如果業務模型比較複雜,那麼上面的這種方式的弊端也是明顯的:

沒有私人環境,所有的屬性都是公開的。

 

今天,我們就業務建模出發,看看如果藉助JavaScript的閉包特性,是否有更好的方式來優雅實現業務建模。

先看一個原型繼承的例子:

 1 var BaseObject = (function(){ 2     var that = {}; 3      4     that.name = ‘Lily‘ ; 5     that.sayHello = function(){ 6         console.log( ‘Hello ‘ + this.getName() ); 7     }; 8     that.getName = function(){ 9         return this.name ;10     };11     12     return that ;13 })();14 15 //建立一個繼承的對象16 var tomObject = Object.create( BaseObject );17 tomObject.name = ‘Tom‘ ;18 19 //調用公開的方法20 tomObject.sayHello( ) ;   //輸出:Hello Tom

【分析】
當前的這種方式,在編碼規範的情況下,是能夠正常工作的,但是,從程式的封裝的角度來看,卻存在明顯的不足。
因為,tomObject也可以設定它的getName函數,
例如:在tomObject.sayHello();之前添加如下代碼:
//....
tomObject.getName = function(){ return ‘Jack‘ };
//調用公開的方法
tomObject.sayHello( ) ; //輸出:Hello Jack

而實際上,作為一個約定,我們希望getName就是調用當前對象的name的屬性值,不允許繼承它的子物件任意覆蓋它!也就是說,getName應該是一個私人函數!
現在,我們看如何用【閉包】來解決這個問題:

 1 var createPersonObjFn = function(){ 2     var that = {}; 3      4     var name = ‘Lily‘ ; 5      6     var getName = function(){ 7         return name ; 8     }; 9     10     that.setName = function( new_name ){11         name = new_name ;12     };13     that.sayHello = function(){14         console.log( ‘Hello ‘ + getName() );15     };16     17     return that ;18 };19 20 //建立一個對象21 var tomObject = createPersonObjFn();22 tomObject.setName( ‘Tom‘ );23 24 //調用公開的方法25 tomObject.sayHello( ) ;   //輸出:Hello Tom

【分析】
現在好了,儘管你還是可以給tomObject增加新的getName()函數,但並不會影響sayHello的商務邏輯。同理,
//...
tomObject.setName( ‘Tom‘ );
tomObject.getName = function(){return ‘Jack‘; }; //設定對象的getName的函數

//調用公開的方法
tomObject.sayHello( ) ;                                      //依然輸出:Hello Tom

閉包的特點就是:
1. 將要‘業務對象‘的屬性儲存在‘運行時環境‘中。
2. 天然的‘原廠模式‘,要新產生一個對象,就執行一下函數。
從這也可以看出,採用‘閉包‘這種模式構建業務時,對於‘原型鏈‘的理解要求並不高,這也許是為什麼老道在它的書中對於‘原型鏈‘著墨甚少的原因吧。

【最佳化】
但是,我們知道,在業務模型中,我們還是希望能夠實現‘繼承‘的效果,也就是說,"主體對象"實現基本的架構和邏輯,"子物件"根據自身的特點來自訂一些特定的行為。通過Object.create() 建立對象時,基於"原型鏈"的特徵,我們很好理解,只要在新建立的對象中重新定義一下自訂函數就可以了。但是,同樣的業務訴求,在‘閉包‘這種方式下如何?呢?

[方法]
在閉包對外公開的函數中,調用通過this調用的函數,那麼這個函數的行為就可以在閉包之外被自訂。
實驗代碼如下:

 1 that.sayHello = function(){ 2     //這裡的sayHello調用了當前對象的getNewName() 3     console.log( ‘Hello ‘ + this.getNewName() );    4 }; 5  6 //...前面其他的代碼不變 7 var tomObject = createPersonObjFn(); 8 tomObject.getNewName = function(){   //定義當前對象的getNewName,  9     return ‘Jack‘ ;10 }11 12 //調用公開的方法13 tomObject.sayHello( ) ;              //輸出:Hello Jack

【分析】
雖然通過修改sayHello中的定義(通過調用方法函數),我們似乎能夠自訂對象的一些行為,但是,新定義的行為並不能訪問到tomObject的私人屬性name!這和對象原來想表達的內容完全沒有關係。而我們真實的業務訴求或許是這樣,自訂行為之後,sayHello 能夠列印"Hello dear Tom!" 或者"Hello my Tom!" 的內容。
[回顧]我們知道,在閉包中,如果要想訪問私人屬性,必須要定義相關的公開的方法。所以,我們最佳化如下:

 1 //...在閉包中,將getName這樣的函數由私人函數轉換為公開函數 2 that.getName = function( ){ 3     return name ; 4 } 5  6 //...定義tomObject的自訂函數getNewName,在函數中調用getName的方法。 7 tomObject.getNewName = function(){ 8     return ‘dear ‘ + tomObject.getName() + ‘!‘ ; 9 }10 tomObject.setName( ‘Tom‘ );11 12 //調用公開的方法13 tomObject.sayHello( ) ;   //輸出:Hello dear Tom!14 15 16 //為了體現自訂行為的特點,我們再建立另外一個Jack的對象17 var jackObject = createPersonObjFn();18 jackObject.getNewName = function(){   //定義當前對象的getNewName, 19     return ‘my ‘ + jackObject.getName() + ‘!‘ ;20 }21 jackObject.setName( ‘Jack‘ );22 23 //調用公開的方法24 jackObject.sayHello( ) ;   //輸出:Hello my Jack!

【分析】
看起來似乎沒有什麼問題了,但是,還有一個小細節需要最佳化。我們在sayHello中調用了this.getNewName();但是,如果新建立的對象沒有重新定義getNewName函數,
那樣豈不報異常了?所以,嚴謹的做法應該是,在閉包中也設定一個that.getNewName的函數,預設的行為就是返回當前的name值,
如果要進行自訂行為,則對象會體現出自訂的行為,覆蓋(重載)預設的行為。

【完整的例子】
1. 在閉包中,可以定義私人屬性(指:對象、字串、數字、布爾類型等),這些屬性只能通過閉包開放的函數訪問、修改。
2. 有些函數,你並不希望外部對象對它進行調用,僅僅供閉包內的函數(包括:公開函數和私人函數)調用,則可以將它定義為私人函數。
3. 如果要想閉包對象的某一部分行為可以自訂(達到繼承的效果),則需要進行如下幾步。
  a. 新增能訪問私人屬性的公開函數,例如:例子中的getName函數。
         因為根據範圍的特點,閉包外部是無法訪問到私人屬性的,而自訂的函數是在閉包外部的。
     b. 在閉包內部,以公開函數的方式,設定需要自訂函數的預設行為,例如:閉包中getNewName函數的定義。
     c. 在允許自訂行為的公開函數(例如:例子中的sayHello函數)中,通過this調用可以自訂行為的函數。
         例如例子中的this.getNewName()。

完整的代碼如下:

 1 var createPersonObjFn = function(){ 2     var that = {}; 3      4     var name = ‘Lily‘ ; 5      6     that.getName = function(){ 7         return name ; 8     }; 9     that.setName = function( new_name ){10         name = new_name ;11     };12     that.getNewName = function( ){   //預設的行為13         return name ;14     };15     that.sayHello = function(){16         console.log( ‘Hello ‘ + this.getNewName() );17     };18     19     return that ;20 };21 22 //1. 建立一個對象23 var tomObject = createPersonObjFn();24 tomObject.getNewName = function(){25     return ‘dear ‘ + tomObject.getName() + ‘!‘ ;26 }27 tomObject.setName( ‘Tom‘ );28 29 //調用公開的方法30 tomObject.sayHello( ) ;   //輸出:Hello dear Tom!31 32 //2. 建立另外一個Jack的對象33 var jackObject = createPersonObjFn();34 jackObject.getNewName = function(){   //定義當前對象的getNewName, 35     return ‘my ‘ + jackObject.getName() + ‘!‘ ;36 }37 jackObject.setName( ‘Jack‘ );38 39 //調用公開的方法40 jackObject.sayHello( ) ;   //輸出:Hello my Jack!41 42 43 //3 建立另外一個Bill的對象,不重新定義getNewName函數,採用預設的行為44 var billObject = createPersonObjFn();45 billObject.setName( ‘Bill‘ );46 47 //調用公開的方法48 billObject.sayHello( ) ;   //輸出:Hello Bill

【總結】

JavaScript是一個表現力很強的語言,非常的靈活,自然也比較容易出錯。上面舉的例子中,我們僅僅突出展現了閉包的特性,其實,利用“原型鏈”的特性,我們完全可以基於tomObject,jackObject這些對象再來建立另外的對象,或者tomObject這些對象的建立過程,放到另外一個閉包中,這樣或許可以組合出更加豐富的模型。閉包的特性就在這裡,原型鏈的特性也在這裡......到底什麼時候用?怎麼組合起來用?關鍵還是看我們的業務訴求,看真實的使用情境,看我們對效能,擴充性,安全等等多個方面的期望。

另外,本文涉及到一些背景知識,例如:原型鏈是怎樣的一個圖譜關係?new這個運算子在建立對象時都做了啥?Object.create又可以如何理解? 由於篇幅有限,就沒有展開來講,如有疑問或建議,歡迎指出討論,謝謝。

【再思考】
細心的同學或許發現了,既然閉包中that.getNewName和that.getName的實現都完全一樣,為什麼要重複定義這兩個函數呢?是不是可以把閉包中that.getName給刪除掉呢?
答案當然是否定的。如果刪除了閉包中的that.getName,而你又重新定義了that.getNewName的方法,這時候,閉包中的私人屬性name在閉包外就沒法訪問到了。
這就像同一包紙巾中的紙,樣子完全一樣,但職責不同,有些是事前用的,有些則是事後用的。
比如,你在公園裡吃蘋果,沒有水果刀,你會先抽出一張紙(A)擦一下蘋果的外表,吃完蘋果之後,把蘋果的核用紙包起來扔到垃圾桶,又抽出一張紙(B)擦一下嘴巴和手。
因為大家都是講衛生,懂文明的"四有新人"。
今天的分享到此為止,感謝大家捧場,希望諸位大俠不吝賜教。

 

業務建模 之 閑話'閉包'與'原型繼承'

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.