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 Composite var 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 FormItemIterface var CompositeImpl = function(){ } //(3)實現接口的方法 implements methods CompositeImpl.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設計模式中接口的實現相關介紹,希望對大家的學習有所幫助。