想象建立wiki的庫。wiki網站包含使用者可以互動式地建立、删除和修改的内容。許多wiki都以簡單、基于文本标記語言建立内容為特色。通常,這些标記語言隻提供了HTML可用功能的一個子集,但是卻有一個更簡單、更清晰的源格式。例如,環繞星号的文本被格式化為粗體,環繞下劃線的被格式化為帶有下劃線的文本,環繞斜杠的被格式化為斜體。用記可以輸入如下格式:
this sentence contains a *bold phrase* within it.
this sentence contains a _underlined phrase_ within it.
this sentence contains a /italicized phrase/ within it.
該網站會以下面的形式将内容呈現給wiki讀者
this sentence contains a bold phrase within it.
this sentence contains a underlined phrase within it.
this sentence contains a italicized phrase within it.
一個靈活的wiki庫應該給應用程式編寫者提供可供選擇的标記語言。
要給應用程式編寫者提供備選的标記語言,需要将提取使用者建立的标記源文本内容的功能與其他功能分離。其他的wiki功能包括賬記管理、修訂曆史記錄和内容存儲等。其餘的應用程式可以通過一套文檔完善的屬性和方法的接口與提取出的功能互動。通過針對接口完備API的嚴格程式設計,并忽略這些方法的實作細節,進而不論選擇使用哪個源格式,應用程式都能正常運作。
更加深入地了解需要什麼樣的wiki内容提取接口。wiki庫必須能提取中繼資料,如頁面标題和作者資訊等,并能将頁面内容格式化為HTML呈現給wiki讀者。可以将wiki中的每一個頁面表示為提供了通過getTitle、getAuthor和toHTML頁面方法擷取這些資料的一個對象。
接下來,該wiki庫需要提供建立一個自定義wiki格式化器的應用程式的方法,以及一些針對流行标記格式的内置格式化器。例如,應用程式的編寫者可能希望使用MediaWiki格式化器(這是維基百科所使用的格式)。
var app=new Wiki(Wiki.formats.MEDIAWIKI);
該wiki庫将該格式化函數存儲在wiki執行個體對象的内部
function Wiki(format){
this.format=format;
}
每當讀者想要檢視頁面時,應用程式都會檢索出其源文本并使用内部的格式化器将源文本渲染為HTML頁面。
Wiki.prototype.displayPage=function(source){
var page=this.format(source);
var title=page.getTitle();
var author=page.getAuthor();
var output=page.toHTML();
//...
}
類似Wiki.formats.MEDIAWIKI的格式化器是如何實作的?熟悉基于類程式設計的程式員可能傾向于建立一個Page的基類,該Page類表示使用者建立的内容,每個Page的子類實作不同的格式。MediaWiki格式化可能實作為一個繼承Page的MWPage類,而MEDIAWIKI則是傳回MWPage執行個體的“工廠函數”。
function MWPage(source){
Page.call(this,source);
//...
}
MWPage.prototype=Object.create(Page.prototype);
MWPage.prototype.getTitle=function(){};
MWPage.prototype.getAuthor=function(){};
MWPage.prototype.toHTML=function(){};
wiki.formats.MEDIAWIKI=function(source){
return new MWPage(source);
}
這裡由于MWPage類需要自己實作所有wiki應用程式需要的getTitle,getAuthor,toHTML方法,Page基類并沒有起到什麼作用。而且displayPage方法并不需要關心頁面對象的繼承體系。它隻需要實作如何顯示頁面的相關方法。wiki格式的實作很自由,任何能完成功能的代碼都可以。
使用簡單對象字面量建構MediaWiki頁面格式的接口實作通常已經足夠了。
Wiki.formats.MEDIAWIKI=function(source){
//....
return {
getTitle:function(){},
getAuthor:function(){},
toHTML:function(){}
}
};
繼承有時會導緻比它解決的更多的問題。當幾個不同的wiki格式共享不相重疊的功能集時,繼承的問題就出現了。沒有任何繼承結構才是對的。例如:
Format A:*bold*,[link],/italics/
Format B:**bold**,[[link]],*italics*
Format A:**bold**,[link],*italics*
我們想要實作各個部分的功能來識别每種不同類型的輸入,然而功能的混合和比對并沒有映射到任何清晰的A,B和C之間的繼承層次關系。正确的做法是分别實作每種輸入比對的函數,然後根據每種格式的需要混合和比對功能。
注意消除Page基類,這裡不需要用任何東西來替代它。任何人希望實作一個新的自定義格式都可以,而不需要在某處“注冊”它。隻要displayPage方法結構正确,具有預期行為的getTitle,getAuthor,toHTML方法,那麼它就适用任何js對象。
這種接口有時稱為結構類型或鴨子類型。任何對象隻要具有預期的結構就屬于該類型(看起來像隻鴨子,或叫聲像隻鴨子)。在js中這是一種優雅、輕量的程式設計模式,因為不需要編寫顯示的聲明。一個調用某個對象方法的函數能夠與任何實作了相同接口的對象一起工作。當然你在API的文檔中列出對象接口的預期結構。這樣接口實作者便會知道哪些屬性和方法是必需的以及庫和應用程式期望的行為是什麼。
靈活的結構類型的另一個好處是有利于單元測試。wiki庫可能期望嵌入一個HTML伺服器對象來實作wiki網站的網絡功能。如果想要在沒有連接配接網絡的情況下測試wiki網站的互動時序,那麼可以實作一個mock對象來模拟HTTP伺服器的行為。這些行為是遵照預定的腳本而不是真實的接觸網絡。這種方式提供了與虛拟伺服器重複互動的行為,而不是依賴不可預知的網絡行為。這使用測試元件與伺服器的互動行為成為可能。
提示
- 使用結構類型(也稱為鴨子類型)來設計靈活的對象接口
- 結構接口更靈活、更輕量,是以應該避免使用繼承
- 針對單元測試,使用mock對象即接口的替代實作來提供可複檢的行為
版權聲明
翻譯的文章,版權歸原作者所有,隻用于交流與學習的目的。
原創文章,版權歸作者所有,非商業轉載請注明出處,并保留原文的完整連結。