1、介面概述
1)、什麼是介面?
介面是提供了一種用以說明一個對象應該具有哪些方法的手段。儘管它可以表明這些方法的語義,但它並不規定這些方法應該如何?。
2)、 介面之利
介面可以告訴程式員一個類實現了哪些方法,從而協助其使用這個類。
- 有助於穩定不同類之前的通訊方式。
- 測試和調式因此也能變得更輕鬆。
在javascript這種弱類型語言中,類型不符錯誤很難跟蹤。使用介面可以讓這種錯誤的尋找變午更容易一點,因為此時如果一個對象不像所要求的類型,或者沒有實現必要的方法,那麼你會得到包含有用資訊的明確的錯誤提示。這樣一來,邏輯錯誤可以被限制在方法自身,而不是在對象構成之中。
因為對介面的任何改變在所有實現它的類都必須體現出來。如果介面添加了一個操作,而某個實現它的類並沒有相應的添加這個操作,那麼你肯定會立即見到一個錯誤。
3)、介面之弊
javascript是一種具有極強表現圖片的語言,這主要得益於其弱類型的特點。而介面的使用則一定程式上強化了類型的作用。這降低了語言的靈活性。javascript並沒有提供對介面的內建支援,而試圖模仿其它語言內建的功能總會有一些風險。
js中介面使用的最大問題在於,無法強迫其他程式員遵守你定義的介面。在其它語言中,介面的概念是內建的,如果某人定義了實現一個介面的類,那麼編譯器會確保該類的確實現了這個介面。而在javascript中則必須用手工的辦法保證某個類實現了一個介面。編碼規範和輔助類可以提供一些協助,但無法徹底根除這個問題。如果項目的其他程式員不認真對待介面,那麼這些介面的使用是無法得到強制性保證的。除非項目的所有人都同意使用介面並對其進行檢查,否則介面的很多價值都無從體現。
2、在javascript中模仿介面
javascript中模仿介面的三種方法:註解描述法、屬性檢查法、鴨式辨型法。
沒有哪種技術是完美的,但三者結合使用基本上可以令人滿意。
1)、注釋描述法實現介面
用注釋模仿介面是最簡單的方法,但效果卻是最差的。這種方法模仿其他頁面對象語言中的做法,使用了interface和implements關鍵字,但把它們放在注釋中,以免引起語法錯誤。如下:
//javascript中定義介面的方式有三種://1、註解描述的方式 /** * interface Composite{* function add(obj);* function remove(obj);* function update(obj);}優點:程式員可以有參考缺點:缺點一大堆,他只是一個借口的文檔範疇,假如不實現 所有的方法,程式照樣可以運行,太鬆散了。對測試和調試難度大*/// Implement of interface Compositevar CompositeImpl =function(){ /*this.add = function(obj){ }; this.remove = function(obj){ }; 這種函數定義的方法,在執行個體化一個對象的時候,new 一個樣本,將產生一個方法,且各個實力的方法還不一樣。 所以採用下面的方法: */ CompositeImpl.prototype.add = function(obj){ } CompositeImpl.prototype.remove = function(obj){ } CompositeImpl.prototype.update = function(obj){ }}var c1 = new CompositeImpl();var c2 = new CompositeImpl()alert(c1.add == c2.add)
這種模仿並不是很好。它沒有為確保Composite真正實現了正確的方法集而進行檢查,也不會拋出錯誤以告知程式員程式中的問題。說到底它主要還是屬於程式文檔範疇。在這種做法中,對介面約定的遵守完全依靠自覺。
2)、屬性檢測法實現介面
這種方法更嚴謹一點。所有類都明確地聲明自己實現了哪些介面,那些想與這些類打交道的對象可能針對這些聲明進行檢查。那些介面自身仍然只是注釋,但現在你可以通過檢查一個屬性得知某個類自稱實現了什麼介面。
/** * interface Composite{ * function add(obj); * function remove(obj); * function update(obj); * } * interface FormItem{ * function select(obj); * } */ // CompositeImpl implements interface Composite,FormItem var CompositeImpl =function(){ //顯示在類的內部,接收所實現的介面,一般來說,這是一個規範, // 我們專案經理:在內部類定義一個數組,名字要固定 this.interfaceImplments = ['Composite','FormItem']; CompositeImpl.prototype.add = function(obj){ alert("小平果"); } CompositeImpl.prototype.remove = function(obj){ } CompositeImpl.prototype.update = function(obj){ } /*CompositeImpl.prototype.select = function(obj){ }*/ } //定義函數檢測,判斷當前對象是否實現了所有的介面 function checkCompositeImpl (instance){ if (!isImplments(instance,'Composite','FormItem')) { throw new Error('Object cannot implements all the interface'); }; } //公用的具體檢測方法(核心方法),主要目的就是判斷樣本對象有沒有實現相關的介面; function isImplments(object){ //arguments 對象會的函數的實際對象 for (var i = 1, len = arguments.length; i < len; i++) { //注意這裡從1開始,逐個方法判斷。 var interfaceName = arguments[i]; //接收實現每一個介面的名字 var interfaceFound = false;//判斷此方法到底是實現了還是失敗了?規範裡定義了interfaceImplments. for (var j = 0;j < object.interfaceImplments.length; j++) { if(object.interfaceImplments[j] == interfaceName){ interfaceFound = true; break; } }; //如果沒有實現,則返回false if (!interfaceFound) { return false; }; } return true; }var c1 = new CompositeImpl();checkCompositeImpl(c1);c1.add();
這個例子中,CompositeImpl 宣稱自己實現了Composite介面,其做法是把這兩個介面名稱加入一個名為implementsInterfaces的數組。類顯式聲明自己支援什麼介面。任何一個要求基於參數屬於特定類型的函數都可以對這個屬性進行檢查,並在所需介面未在聲明之列時拋出一個錯誤。
這種方法有幾個優點。它對類所實現的介面提供了文檔說明。如果需要的介面不在一個類宣稱支援的介面之列,你會看到錯誤訊息。通過利用這些錯誤,你可以強迫其他程式員聲明這些介面。
這種方法的主要缺點在於它並未確保類真正實現了自稱實現的介面。你只知道它是否說自己實現了介面。在建立一個類時聲明它實現了一個介面,但後來在實現該介面所規定的方法時卻漏掉其中的某一個,這種錯誤很常見。此時所有檢查都能通過,但那個方法卻不存在,這將在代碼中埋下一個隱患。另外顯式聲明類所支援的介面也需要一些額外的工作。
3)、鴨式辨型法實現介面
其實,類是否聲明自己支援哪些介面並不重要,只要它具有這些介面中的方法就行。鴨式辨型(這個名稱來自James Whitomb Riley的名言:“像鴨子一樣走路並且嘎嘎叫的就是鴨子”)正是基於這樣的認識。它把對象實現的方法集作作為判斷它是不是某個類的執行個體的唯一標準。這種技術在檢查一個類是否實現了某個介面時也可大顯向身手。這種方法背後的觀點很簡單:如果對象具有與介面定義的方法同名的所有方法,那麼就可以認為它實現了這個介面。你可以用一個輔助函數來確保對象具有所有必需的方法:
/* 實現介面的第三種方式:鴨式辨型發實現介面,(較為完美的實現方法) 核心思想:一個類實現介面的主要目的:把其中的方法都實現了(檢測方法) 完全物件導向 代碼實現統一,實現解耦*///1、介面類---Class Interface ===>執行個體化N多個介面/** *介面類的參數?幾個 * 參數1:介面名 * 參數2:接收方法的集合(數組) */var Interface = function(name , methods){ //判斷介面的參數個數 if (arguments.length !=2) { throw new Error('the instance interface constructor arguments should be 2'); }; this.name =name; //this.methods = methods; this.methods = []; for (var i = 0, len = methods.length; i <len; i++) { if (typeof methods[i] !== "string"){ throw new Error('the name of method is wrong'); } this.methods.push(methods[i]); } }//2、準備工作,具體的實現//(1)執行個體化介面對象var CompositeInterface = new Interface('CompositeInterface',['add','delete']);var FormItemInterface = new Interface('FormItemInterface',['update','select']); //(2)具體的實作類別//CompositeImpl implments CompositionIterface FormItemIterfacevar CompositeImpl = function(){}//(3)實現介面的方法 implements methodsCompositeImpl.prototype.add = function(obj){ alert("add");}CompositeImpl.prototype.delete = function(obj){ alert("delete");} CompositeImpl.prototype.update = function(obj){ alert("update");}/*CompositeImpl.prototype.select = function(obj){ alert("select");}*///3、檢驗介面裡的方法//如果檢測通過,不做任何操作;不通過,則拋出異常。//這個方法的目的就是 檢測方法的Interface.ensureImplements =function(object){ //如果接受參數長度小於2 ,證明還有任何實現的介面 if (arguments.length < 2) { throw new Error('The Interface has no implement class'); }; //獲得介面的執行個體對象 for (var i = 1, len= arguments.length; i < len; i++) { var instanceInterface =arguments[i]; //判斷參數是否為 介面類的類型 if (instanceInterface.constructor !==Interface) { throw new Error('The arguments constructor is not Interface Class'); }; for (var j = 0, len2 =instanceInterface.methods.length ; j <len2; j++ ) { //用一個臨時變數 ,接收每個方法的名字(注意為字串類型) var methodName = instanceInterface.methods[j]; //object[key] 獲得方法 if (!object[methodName] || typeof object[methodName] !== 'function') { throw new Error('the method"'+ methodName+'"is not found'); } } }}var c1 =new CompositeImpl();Interface.ensureImplements(c1,CompositeInterface,FormItemInterface);c1.add();
與另外兩種方法不同,這種方法並不藉助注釋。其各個方面都是可以強制實施的。ensureImplements函數需要至少兩個參數。第一個參數是想要檢查的對象。其餘參數是據以對那個對象進行檢查的介面。該函數檢查其第一個參數代表的對象是否實現了那些介面所聲明的所有方法。如果發現漏掉了任何一個方法,它就會拋出錯誤,其中包含了所缺少的那個方法和未被正確實現的介面的名稱等有用資訊。這種檢查可以用在代碼中任何需要確保某個對象實現了某個介面的地方。在本例中,addForm函數僅當一個表單對象支援所有必要的方法時才會對其執行添加操作。
儘管鴨式辨型可能是上述三種方法中最有用的一種,但它也有一些缺點。這種方法中,類並不聲明自己實現了哪些介面,這降低了代碼的可重用性,並且也缺乏其他兩種方法那樣的自我描述性。它需要使用一個輔助類Interface和一個輔助函數ensureImplements。而且,它只關心方法的名稱,並不檢查其參數的名稱、數目或類型。
3、Interface類的使用場合
嚴格的類型檢查並不總是明智的。許多js程式員根本不用介面或它所提供的那種檢查,也照樣一幹多年。介面在運用設計模式實現複雜系統的時候最能體現其價值。它看似降低javascript的靈活性,而實際上,因為使用介面可以降低對象間的耦合程度,所以它提高了代碼的靈活性。介面可以讓函數變得更靈活,因為你既能向函數傳遞任何類型的參數,又能保證它只會使用那些具有必要方法的對象。
4、Interface類的用法
判斷代碼中使用介面是否划算是最重要的一步。對於小型的、不太費事的項目來說,介面的好處也許並不明顯,只是徒增其複雜度而已。你需要自行權衡其利弊。如果認為在項目中使用介面利大於弊,那麼可以參照如下使用說明:
1)、 將Interface類納入HTML檔案。
2)、 逐一檢查代碼中所有以對象為參數的方法。搞清代碼正常運轉要求的這些對象參數具有哪些方法
3)、 為你需要的每一個不同的方法集建立一個Interface對象。
4)、 剔除所有針對構造器顯式檢查。因為我們使用是鴨式辨型,所以對象的類型不再重要。
5)、 以Interface.ensureImplements取代原來的構造器檢查。
樣本
假設你要建立一個類,它可以將一些自動化測試結果轉化為適於在網頁上查看的格式。該類的構造器以一個TestResult類的執行個體為參數。它會應客戶的請求對這個TestResult對象所封裝的資料進行格式化,然後輸出。
原始定義:
var ResultFormatter =function(resultsObject){ if(!(resultsObject instanceof TestResult)){ throw newError("ResultsFormatter:constructor requires an instance of TestResult asan argument.") } this.resultsObject = resultsObject; } ResultFormatter.prototype.renderResults =function(){ var dateOfTest = this.resultsObject.getDate(); var resultsArray =this.resultsObject.getResults(); var resultsContainer =document.createElement('div'); var resultsHeader =document.createElement("h3"); resultsHeader.innerHTML = "TestResults from "+dateOfTest.toUTCString(); resultsContainer.appendChild(resultsHeader); var resultList =document.createElement("ul"); resultsContainer.appendChild(resultList); for(var i=0,len=resultsArray.length;i<len;i++){ var listItem=document.createElement('li'); listItem.innerHTML =resultsArray[i]; resultList.appendChild(listItem); } return resultsContainer; }
該類的構造器會對參數進行檢查,以確保其的確為TestResult類的執行個體。如果參數達不到要示,構造器將拋出一個錯誤。有了這樣的保證,在編寫renderResults方法時,你就可以認定有getDate和getResults這兩個方法可供使用。實際上這並不能保證所需要的方法得到了實現。TestResult類可能會被修改,致使其不再擁有getDate()方法。在此情況下,構造器中的檢查仍能通過,但renderResults方法卻會失靈。
此外,構造器的這個檢查施加了一些不必要的限制。它不允許使用其他類的執行個體作為參數,哪怕它們原本可以如願發揮作用。例如,有一個名為WeatherData在也擁有getDate和getResults這兩個方法。它本來可以被ResultFormatter類用得好好的。但是那個顯式類型檢查會阻止使用WeatherData類的任何執行個體。
問題解決辦法是刪除那個使用instanceOf的檢查,並用介面代替它。首先,我們需要建立這個介面:
//ResultSetInterface.var ResultSet =new Interface(“ResultSet”,[‘getDate','getResults']);
上面的這行代碼建立了一個Interface對象的新執行個體。第一個參數是介面的名稱,第二個參數是一個字串數組,其中的每個字串都是一個必需的方法名稱。有了這個介面之後,就可以用介面檢查替代instanceOf檢查了
var ResultFormatter = function(resultsObject){ Interface.ensureImplements(resultsObject,ResultSet); this.resultsObject = resultsObject;}ResultFormatter.prototype.renderResults= function(){ …}
renderResults方法保持不變。而構造器則被改為使用ensureImplements方法而不是instanceof運算子。現在構造器可以接受WeatherData或其他任何實現所需要方法的類的執行個體。我們只修改了幾行ResultFormatter類代碼,就讓那個檢查變得更準確,而且更寬容。
5、依賴於介面的設計模式
以上就是JavaScript設計模式中介面的實現相關介紹,希望對大家的學習有所協助。