JavaScript設計模式 Item 2 -- 接口的實現

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,FormItemvar 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;}};//如果沒有實現,則返回falseif (!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、依賴于接口的設計模式

  • 工廠模式
  • 組合模式
  • 裝飾模式
  • 命令模式

版權聲明:本文為小平果原創文章,轉載請注明:http://blog.csdn.net/i10630226

轉載于:https://www.cnblogs.com/dingxiaoyue/p/4948175.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458713.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458713.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458713.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring Boot 樂觀鎖加鎖失敗 - 集成AOP

Spring Boot with AOP 手頭上的項目使用了Spring Boot&#xff0c; 在高并發的情況下&#xff0c;經常出現樂觀鎖加鎖失敗的情況&#xff08;OptimisticLockingFailureException&#xff0c;同一時間有多個線程在更新同一條數據&#xff09;。為了減少直接向服務使用者直接返回…

掌握VS2010調試 -- 入門指南

1 導言 在軟件開發周期中&#xff0c;測試和修正缺陷&#xff08;defect&#xff0c;defect與bug的區別&#xff1a;Bug是缺陷的一種表現形式&#xff0c;而一個缺陷是可以引起多種Bug的&#xff09;的時間遠多于寫代碼的時間。通常&#xff0c;debug是指發現缺陷并改正的過程。…

151031

create or replace procedure pr_test1 is v_case number(3): 100; beginif 2>1 thendbms_output.put_line(成立);elsif 4>3 thenif 7>6 thendbms_output.put_line(不成立);end if; elsif 6>5 thendbms_output.put_line(也行);elsedbms_output.put_line(也不成立);…

postgresql9.5 run 文件linux安裝后配置成開機服務

網上出現的比較多安裝方法要么是源碼安裝&#xff0c;要么是yum安裝&#xff0c;我發覺都要配置很多屬性&#xff0c;比較麻煩&#xff0c;所以現在我在centos7長用 run文件來安裝 http://get.enterprisedb.com/postgresql/postgresql-9.5.1-1-linux-x64.run 這里的安裝shell整…

Windows API GetProcAddress 及demo code

GetProcAddress函數檢索指定的動態鏈接庫(DLL)中的輸出庫函數地址。 函數原型&#xff1a; FARPROC GetProcAddress( HMODULE hModule, // DLL模塊句柄 LPCSTR lpProcName// 函數名 ); 參數&#xff1a; hModule [in] 包含此函數的DLL模塊的句柄。LoadLibrary、AfxLoadLibrary …

【操作系統】進程管理

進程管理 進程的基本概念 程序的順序執行及其特征 程序的順序執行:僅當前一操作(程序段)執行完后&#xff0c;才能執行后續操作。 程序順序執行時的特征&#xff1a;順序性&#xff0c;封閉性&#xff0c;可再見性。 前趨圖 前趨圖(Precedence Graph)是一個有向無循環圖&#…

va_list va_start va_end的使用

<pre name"code" class"cpp" style"color: rgb(51, 51, 51); white-space: pre-wrap; word-wrap: break-word;"><strong>一、 從printf()開始</strong> 從大家都很熟悉的格式化字符串函數開始介紹可變參數函數。 原型&#xf…

Linux學習之CentOS(三)----將Cent0S 7的網卡名稱eno16777736改為eth0

【正文】 Linux系統版本&#xff1a;CentOS_7&#xff08;64位&#xff09; 一、前言&#xff1a; 今天又從Centos 6.5裝回了Centos 7&#xff0c;畢竟還是要順應潮流嘛。安裝完成之后&#xff0c;發現發現CentOS 7默認的網卡名稱是eno16777736&#xff0c;如圖所示&#xff1a…

本地音頻播放,使用AVFoundation.framework中的AVAudioPlayer來實現

本地音頻播放,使用AVfoundation.framework中的AVAudioPlayer來實現 /*AVAudioPlayer的使用比較簡單: 1、初始化AVAudioPlayer對象&#xff0c;此時通常指定本地文件路徑 2、設置播放器屬性&#xff0c;例如重復次數、音量大小等 3、調用play方法播放。 */

AngularJS操作DOM——angular.element

addClass()-為每個匹配的元素添加指定的樣式類名after()-在匹配元素集合中的每個元素后面插入參數所指定的內容&#xff0c;作為其兄弟節點append()-在每個匹配元素里面的末尾處插入參數內容attr() - 獲取匹配的元素集合中的第一個元素的屬性的值bind() - 為一個元素綁定一個事…

C++中operator的主要用法

1&#xff0e; operator 用于類型轉換函數&#xff1a; 類型轉換函數的特征&#xff1a; 1&#xff09; 型轉換函數定義在源類中&#xff1b; 2&#xff09; 須由 operator 修飾&#xff0c;函數名稱是目標類型名或目標類名&#xff1b; 3&#xff09; 函數沒有參數&#x…

聲紋識別

一、 聲紋識別是一項根據語音波形中反映說話人生理和行為特征的語音參數&#xff0c;自動識別說話人身份的技術。與語音識別不同的是&#xff0c;聲紋識別利用的是語音信號中的說話人身份信息&#xff0c;而不考慮語音中的字詞意思。由于每個人的生物特征具有與其他人不同的唯一…

Asp.net mvc 實時生成縮率圖到硬盤

之前對于縮率圖的處理是在圖片上傳到服務器之后&#xff0c;同步生成兩張不同尺寸的縮率供前端調用&#xff0c;剛開始還能滿足需求&#xff0c;慢慢的隨著前端展示的多樣化&#xff0c;縮率圖已不能前端展示的需求&#xff0c;所以考慮做一個實時生成圖片縮率圖服務。 每次調用…

數據庫事務的隔離機制

數據庫事務(Database Transaction) &#xff0c;是指作為單個邏輯工作單元執行的一系列操作&#xff0c;要么完全地執行&#xff0c;要么完全地不執行。----百度百科就是說你定義一組數據庫操作&#xff0c;然后告訴數據庫說這些操作要么都成功&#xff0c;要么都不成功。類似于…

如何使用CppUnit進行單元測試

http://www.vckbase.com/document/viewdoc/?id1762 一、前言 測試驅動開發(TDD)是以測試作為開發過程的中心&#xff0c;它堅持&#xff0c;在編寫實際代碼之前&#xff0c;先寫好基于產品代碼的測試代碼。開發過程的目標就是首先使測試能夠通過&#xff0c;然后再優化設計結構…

錄制wav格式的音頻

項目中有面部認證、聲紋認證&#xff0c;服務器端要求上傳wav格式的音頻&#xff0c;所以寫了這樣一個小demo。 剛剛開始寫博客還不知道怎么上傳代碼&#xff0c;就復制了&#xff0c;嘻嘻 DotimeManage.h class DotimeManage; protocol DotimeManageDelegate <NSObject&g…

iOS開發網絡篇—Reachability檢測網絡狀態

前言&#xff1a;當應用程序需要訪問網絡的時候&#xff0c;它首先應該檢查設備的網絡狀態&#xff0c;確認設備的網絡環境及連接情況&#xff0c;并針對這些情況提醒用戶做出相應的處理。最好能監聽設備的網絡狀態的改變&#xff0c;當設備網絡狀態連接、斷開時&#xff0c;程…

網絡七層協議 五層模型 TCP連接 HTTP連接 socket套接字

socket&#xff08;套接字&#xff09;是通信的基石&#xff0c;是支持TCP/IP協議的網絡通信的基本操作單元&#xff0c;包含進行網絡通信必須的五種信息&#xff1a;連接使用的協議&#xff0c;本地主機的IP地址&#xff0c;本地進程的協議端口&#xff0c;遠地主機的IP地址&a…

[vs2010 project] CppUnit快速入門

簡介 測試是軟件開發過程中極其重要的一環&#xff0c;詳盡周密的測試能夠減少軟件BUG&#xff0c;提高軟件品質。測試包括單元測試、系統測試等。其中單元測試是指針對軟件功能單元所作的測試&#xff0c;這里的功能單元可以是一個類的屬性或者方法&#xff0c;測試的目的是看…

[javascript|基本概念|Number]學習筆記

Number類型的值&#xff1a;整數/浮點數值 整數 十進制 e.g.: var intNum 50; 八進制 (嚴格模式下無效,解析錯誤)字面值首位必須是0,之后的數字序列為0&#xff5e;7 e.g.: var intNum 070; //解析為十進制56 (如果字面值數值超出了范圍&#xff0c;前導0將被忽略&#xf…