這一系列的文章將主要基於js設計模式這本書的要點還有一些翻閱的博客文章,借鑑來源會註明,外加本身的一些demo與直覺.不一樣於其餘設計模式類的書,js設計模式是一本講述設計模式在動態語言js中的實現的書它從設計的角度教人編寫代碼,書中的許多實例代碼來自YUI實戰項目,對js面向對象的特性闡述到位,詳細剖析了面向對象底層實現機制.隨着ajax技術的興起,Web應用的許多業務邏輯從服務器轉移到瀏覽器端執行,,js愈來愈大的規模和複雜程度須要fe rd更好地運用面向對象特性.js設計模式 和 ajax設計模式 作個比較:後者研究了運用ajax設計模式開發Web應用的各類設計模式,前者研究了通用面向對象設計模式在js中的實現,不只僅用於瀏覽器端編程,還能夠有其餘環境下的實現.
本書比較大的缺點在於做者闡述對各類設計模式的實現時沒有選擇js面向對象編程最天然的繼承實現方式原型繼承,而是選擇了該書出版時廣大讀者較爲熟悉的類繼承.
設計模式運用在java,cpp等等各種程序語言上已經有許多年了,應用於各類語言和語法上表現出一致性,基本結構是相同的,只是細節上略有差異,但對js則不同,那些常見的面向對象特性還須要靠晦澀的技巧來實現,因此設計模式也一樣須要這樣的手段.html
這本書大致上分爲兩部分,第一部分給出實現具體設計模式所須要的面向對象特性的基礎知識,主要包括接口,封裝,信息隱藏,繼承,單體模式等等.第二部分專一於各類具體的設計模式及其在js中的應用,主要介紹了工廠模式,橋接模式,組合模式,門面模式...幾種常見的模式.java
第一部分前6章是js面向對象的基礎知識,第1章富有表現力的js主要介紹js實現同一任務的編程風格多樣性,函數式編程實現面向對象,運用設計模式的原因.第2章接口分析了js如何採用最佳特性來模仿接口,探討了接口檢查的各類方式,而後給出一個用來檢查對象是否具備必要方法的可重用類.第3章封裝和信息隱藏探討了js中建立對象的各類不一樣方式,以及如何建立public,private,protected方法.第4章繼承講述了js中如何建立子類,對比了類繼承和原型繼承及其應用場合.第5章單體模式討論了js中的單體模式,命名空間,代碼組織以及分支技術--根據運行時環境動態定義方法.第6章方法的鏈式調用,對有沒有采用該技術來建立一個js庫做比較.node
第7章工廠模式,它有助於消除那些彼此實例化對方的類之間的耦合,而後改用一個方法來肯定要實例化哪一個類.主要討論了兩點,一個是如何用一個類(一般是一個單體)來生成實例的簡單工廠模式,一個是如何用子類來肯定一個成員對象應該是哪一種具體類實例的工廠模式.第8章橋接模式討論了一種把兩個對象鏈接在一塊兒並且還能避免兩者間強耦合的方法.橋接元素容許兩個對象獨立變化,演示瞭如何用橋接元素把函數鬆散得綁定到事件.第9章組合模式很是適合於建立Web動態頁面,演示瞭如何達到只用一條命令便可在許多對象上激發複雜或者遞歸性的行爲,以及如何把一系列對象組織成複雜的層次體系.第10章門面模式用來爲對象建立一個更完善的接口,把現有接口轉換爲一個更便於使用的接口.這一章解釋了大多數js庫都是爲js在具體瀏覽器中的實現提供的一個門面,也闡述瞭如何用這種模式建立便利方法和事件工具庫.第11章適配器模式可讓現有接口契合實際須要.適配器也稱包裝器,用來把不匹配的接口替換爲一個可用於現有系統中的接口.該章探討了如何用適配器彌補js庫之間的差別並金華一中庫到另外一種庫之間的過渡過程,分析了一個電子郵件API並建立了一個有助於升級到新版本的適配器.第12章裝飾者模式引入了一種能夠爲對象添加屬性而沒必要建立新的子類的方法.用於把對象透明地包裝到另外一種具備相同接口的對象中.還研究了裝飾者的結構以及如何將其與工廠模式結合以自動建立內嵌對象,該章有一個性能分析器示例,示範如何用裝飾者模式動態實現接口.第13章享元模式用於優化,該章示範瞭如何經過把打屁獨立對象轉變爲少許共享對象以大幅削減實現應用軟件所需的對象數目,該章還建立了一個Web日曆和一個可重用的工具提示類,示範如何按照享元模式進行重構.第14章代理模式用於控制對象的訪問代理.該章研究瞭如何爲本體real object建立一個代理對象以做爲替身,並使其能被遠程訪問,還探討了該模式的種種用法.第15章觀察者模式對對象的狀態進行觀察,發生變化時能獲得通知.也稱爲發佈者-訂閱者publisher-subscriber模式,用於讓對象對事件進行監聽以便對其做出響應,該章討論了在使用一個動畫庫時能夠訂閱的各類事件.第16章命令模式對方法調用進行封裝:進行參數化和傳遞,而後在須要的時候再加以執行.應用場合主要包括建立建立用戶界面--尤爲是那種須要不受限制的取消操做的用戶界面.該章還討論了該模式的結構.第17章職責蓮模式用來消除請求的發送者和接受者之間的耦合,研究瞭如何使用該模式處理事件的捕獲和冒泡,如何建立弱耦合模塊和優化事件監聽器的綁定.git
對於書中的代碼實例,做者使用了一些簡易函數來執行安裝事件監聽器,派生子類,處理Cookie,引用HTML元素等任務,沒有使用YUI或者jQuery等js庫來提供相似功能,讀者可使用本身喜歡的任何庫結合使用.下面是這些簡易函數的說明:程序員
$(id) 根據ID獲取HTML元素的引用,其參數能夠是字符串,字符串的數組等等github
addEvent(obj, type, func), 把函數fund做爲元素obj的事件監聽器,type表示該函數監聽的事件.ajax
addLoadEvent(func),將函數fund關聯到window對象的load事件.編程
getElementByClass(searchClass, node, tag),獲取全部class屬性值爲searchClass的元素的引用.node和tag這兩個參數無關緊要,能夠用來縮小搜索範圍,函數的返回值是一個數組.設計模式
insertAfter(parent, node, referenceNode),插入元素node,其父元素爲parent,其位置在referenceNode以後.數組
getCookie(name),獲取與名爲name的Cookie相關聯的字符串.
setCookie(name, value, expires, path, domain, secure),把與名爲name的Cookie相關聯的字符串設置爲value,除name和value外的其它參數都可有可無.
deleteCookie(name),將名爲name的Cookie的過時時間設置到過去,即刪除這個Cookie.
clone(object),建立object的一個副本,用於原型繼承,見第4章.
extend(subClass, superClass),執行一些必要的工做,使SubClass成爲superClass的子類,見第4章.
augment(receivingClass, givingClass),將givingClass中的方法輸入receivingClass中,見第4章.
js編程風格多樣化,既能夠函數式編程,也能夠面向對象式.能夠模仿其餘語言的編程模式和慣用法.以啓動和中止動畫爲例,定義一個類,用來建立能夠保存狀態而且具備一些僅對其內部狀態進行操做的方法的動畫對象:
//Anim Class var Anim = function () { ... }; Anim.prototype.start = function () { ... }; Anim.protype.stop = function () { ... } //Usage var myAnim = new Anim(); myAnim.start(); ... myAnim.stop();
上面的代碼定義了一個名爲Anim的類,並把兩個方法賦給該類的prototype屬性.也能夠更便捷地把類的定義封裝在一條聲明裏面:
//Anim class, with a slightly different syntax for declaring methods. var Anim = function() { ... } Anim.prototype = { start: function () { ... }, stop: function () { ... } };
換種風格:
//Add a method to the function object that can be used to declare methods. Function.prototype.method = function(name, fn) { this.prototype[name] = fn; }; //Anim class, with methods created using a convenience method. var Anim = function () { ... }; Anime.method('start', function () { ... }); Amin.method('stop', function () { ... });
Function.prototype.method用於爲類添加新方法, 它有兩個參數, 第一個是字符串, 表示新方法的名稱; 第二個是用做新方法的函數. 能夠修改一下Function.Prototype.method, 使其能夠被鏈式調用, 這隻須要返回this便可:
//This version allows the calls to be chained. Function.prototype.method = function (name, fn) { this.prototype[name] = fn; return this; }; //Anim class,with methods created using a convenience method and chaining. var Anim = function () { ... }; Anim.method('start', function () { ... }); Anim.method('stop', function () { ... });
以上是完成同一項任務的不一樣方法, 他們的風格略有差別, 不一樣風格在代碼篇幅, 編碼效率, 和執行性能方面各有特色, 不一樣人偏好不一樣的風格.
在js中, 定義變量時沒必要聲明其類型. 可是變量依然具備類型, 一個變量具備的類型取決於其包含的數據. 有三種原始類型: 布爾型, 數值型(不一樣於其餘語言, js不區分整數和浮點數)和字符串型. 另外還有對象類型和包含可執行代碼的函數類型, 前者是一種複合數據類型(數組是一種特殊的對象, 包含着一批值的有序集合),最後還有null空類型和undefined.原始類型按值傳遞,其餘類型按引用傳遞.
js中的變量既能夠根據所賦的值改變類型,還能夠進行原始類型之間的轉換.toString能夠把數值或者布爾值轉換爲字符串.parseFloat和parseInt能夠把字符串轉換爲數值,雙重非能夠把字符串或者數值轉換爲布爾值.
js這樣的弱類型語言變量帶來了極大的靈活性,可是會根據須要進行轉換,通常說來不會有類型錯誤.
js中的函數能夠存儲在變量中,能夠做爲參數傳給其餘函數,能夠作爲返回值從其然函數傳出,還能夠在運行時進行構造.這些特性帶來了極大的靈活性和表達能力.
能夠用function () {...}建立匿名函數,沒有函數名可是能夠賦值給變量.
//An anonymous function, executed immediately. (function () { var foo = 10; var bar = 2; console.log(foo * bar); })();
該函數在定義以後當即執行,甚至不用賦值給一個變量,出如今函數聲明以後的一對括號當即對函數進行調用,實際上括號當中也能夠有參數:
//An anonymous function with arguments. (function (foo, bar) { console.log(foo * bar); )(10, 2); ` 這個匿名函數與前一個等價,只不過變量沒有在函數內部用var聲明,而是做爲參數從外部傳入,函數也能夠返回值,而後賦值給一個變量: `//An anonymous function returns a value. var baz = (funciton(foo, bar) { return foo * bar; })(10, 2); //baz equals to 20.
匿名函數最好的用途是用來建立閉包,closure是一個受到保護的變量空間,由內嵌函數生成,js有函數級的做用域:定義在函數內部的變量在函數外部不能被訪問.js的做用域又是詞法性質的,函數運行在定義它的做用域中,而不是在調用它的做用域中,把這兩個因素結合起來就能經過把變量包裹在匿名函數中加以保護.能夠這樣建立類的private變量:
//An anonymous function used as a closure. var baz; (function () { var foo = 10; var bar = 2; baz = function () { return foo * bar; }; })(); baz(); //baz can access foo and bar, even though it is executed outside of the anonymous function.
變量foo和bar定義在匿名函數中,由於函數baz定義在這個比保重,因此它能訪問兩個變量,即便是在該閉包執行結束後.
在js中,除了布爾,數值,字符串三種原始數據類型(即使是他們也能夠進行包裝爲對象),一切都是易變的對象:爲函數添加屬性等等.
function displayError (message) { displayError.numTimesExecuted++; console.log(message); }; displayError.numTimesExecuted = 0;
你也能夠對先前定義的類和實例化的對象進行修改:
//Class Person. function Person (name, age) { this.name = name; this.age = age; } Person.prototype = { getName: function () { return this.name; }, getAge: function () { return this.age; } } //Instantiate the class. var alice = new Person('Alice', 93); var bill = new Person('Bill', 30); //Moddify the class. Person.prototype.getGreeting = function () { return 'Hi' + this.getName() + '!'; }; //Modify a specific instance. alice.displayGreeting = function () { console.log(this.gerFreeting()); };
這個例子中,類的getGreeting方法是在已經建立了類的兩個實例以後才添加的,但這兩個實例仍然可以得到這個方法,其緣由在於prototype對象的工做機制.對象alice獲得了displayGreeting方法,其餘事裏卻沒有.
與對象易變性相關的還有內省introspection:在運行時檢查所具備的屬性和方法,還可使用這種信息動態實例化類和執行其方法(反射reflection),甚至不須要在開發時知道他們的名稱.書中js大多數用來模仿傳統面向對象特性的技術都依賴於對象的易變性和反射.在C++,Java不能對已經實例化的對象進行擴展,不能對已經定義好的類進行修改,而在js中,任何東西均可以在運行時修改,固然也有弊端:能夠定義一個具備一套方法的類,卻沒法肯定這些方法會一直保持不變,這是js不多進行類型檢查的緣由.
js使用的是基於對象--原型繼承,能夠用來模仿類繼承,編碼時應根據手頭任務的實際狀況和兩種繼承的實際性能表現進行選擇.
1995年GoF出版的"設計模式"整理記錄了對象間相互做用的各類方式,用以建立不一樣類型對象的套路被稱爲design pattern.
js強大的表現力賦予了程序員在運用設計模式編碼時的靈活性,js中使用設計模式的主要緣由以下:
可維護性,有助於下降模塊間的耦合強度,使得重構,更換模塊,大型團隊中的合做變得更容易.
溝通,爲處理不一樣類型的對象提供了一套通用的術語,好比該系統它使用了工廠模式,能夠在較高層面上對帶有特定模式的名稱進行討論,沒必要涉及過多的細節.
性能,他們起優化做用,能夠大幅提升程序的運行速度,減小須要傳送到客戶端的代碼量,最經典的享元模式和代理模式.
你也可能不使用由於以下原因design pattern:
複雜性,得到可維護性每每須要付出代價,新手更不容易理解.
性能,多數模式性能上都有一點點拖累,不過能不能接受取決於項目須要.
懂得應該在何時選擇恰當的模式較爲困難,盲目套用會帶來許多不安全問題.
這一章是基礎概念,研究意義,研究內容等等方面的定位與確認,並不太適合做出相關實踐性的項目或者舉出demo.
js中沒有內置的建立或者實現接口的方法,也沒有內置的方法用於判斷一個對象是否實現了與另外一個對象相同的一套方法,使得對象很難互換使用,不過js很靈活,添加這些特性並不難.
它提供了一種用以說明一個對象具備哪些方法的手段,能夠代表這些方法的語義,可是並不解釋應該如何實現. e.g.若是一個接口包含setName方法,那麼能夠認爲該方法的實現有一個字符串參數,而且會把該參數賦值給name變量.
有了接口,就能按照對象的特性進行分組,即便一批對象存在極大差別,只要他們都實現Comparable接口,那麼在Object.compare(anotherObject)方法中就能夠互換使用這些對象.
描述做用,促進代碼重用.代表一個類實現了哪些方法,從而幫助使用該類.
若是事先知道了接口,就能減小在集成兩個對象的過程當中出現的問題,能夠事先聲明一個類應該具備哪些特性和操做,程序員A針對所須要的類定義一個接口,而後把它交給B,B能夠隨意編碼代碼只要他定義的類實現了那個接口.
測試和調試更爲輕鬆.使用接口可讓類型不匹配錯誤的查找變得更容易,若是一個對象沒有實現必要的方法,就會獲得明確的錯誤提示,邏輯錯誤能夠被限制在方法自身,而不是在對象的構成之中.接口還能讓代碼更穩固,由於對接口的任何改變在全部實現它的類中都必須體現出來,若是接口添加一個操做,某個實現它的類並無相應的添加這個操做,就會報錯.
接口的使用在必定程度上強化了類型的做用,減小了js的靈活性.
js中任何實現接口的方法都會有性能上的一些影響,原因額外的方法調用的開銷,實現方法中使用兩個for循環來遍歷每一個接口的每一個方法,對於大型接口和實現許多不一樣接口的對象會消耗較多時間.
接口的最大問題是無法強迫其它人遵循你定義的接口,其餘語言中接口內置,某人定義了實現一個接口的類,那麼編譯器會確保該類的確實現了這個接口,在js中必須手動地去保證某個類實現了一個接口,編碼規範和輔助類能夠幫忙但沒法根治.只有團隊成員都願意使用接口並進行檢查,接口的不少價值才能體現出來.
以Java爲例,下面是java.io包中的一個接口:
public interface DataOutput { void writeBoolean(boolean value) throws IOException; void writeByte(int value) throws IOException; void writeChar(int value) throws IOException; void writeShort(int value) throws IOException; void writeInt(int value) throws IOException; ... }
它列出了一個類應該實現的一批方法,包括方法的參數和可能會拋出的異常,每一行都像是一個方法聲明,只不過是以一個分號而不是一對大括號結尾.
建立一個實現該接口的類須要使用關鍵字implements:
public class DataOutStream extends FilterOutputStream implements DataOutput { public final void writeBoolean (blooean value) throws IOException { write (value ? 1 : 0); } ... }
該類聲明並具體實現了接口中列出的每個方法,漏掉任何一個方法都會致使在編譯時顯示錯誤.下面是Java編譯器在發現一個接口錯誤時可能產生的輸出信息:
MyClass should be declared abstract; it does not define writeBoolean(boolean) in MyClass.
從上面的Java代碼能夠看出接口包含的信息:須要實現的方法名以及這些方法的參數,類的定義明確地聲明他們實現了這個接口(一般使用implements關鍵字),一個類能夠實現不止一個接口,若是接口中某個方法沒有被實現,則會產生一個錯誤有的語言產生在編譯時,有的在運行時,錯誤包含三類信息:類名,接口名,未被實現的方法名.
由於js中沒有interface和implements關鍵字(es5的嚴格模式把interface視做保留字),不能在運行時對接口約定是否獲得遵照進行檢查,因此咱們能夠經過使用輔助類和顯式檢查來進行模仿.
註釋法,屬性檢查法,鴨式辨型法,三種方法當中沒有完美的,結合起來能夠使人滿意.
最簡單,可是效果最差,使用interface和implements關鍵字,可是放在註釋當中,以避免引發語法錯誤:
/* interface Composite { function add(child); function remove(child); function getChild(index); } interface FormItem { function save(); } */ var CompositeForm = function(id, method, action) { //implements Composite, FormItem ... }; //Implement the Composite interface. CompositeForm.prototype.add = function (child) { ... }; CompositeForm.prototype.remove = function (child) { ... }; //Implement the FormItem interface. CompositeForm.prototype.save = function (child) { ... };
能夠看出,註釋法沒有爲確保CompositeForm實現了全部方法而進行檢查,不拋出錯誤,對測試和調試沒有任何幫助.歸屬於程序文檔範疇,對接口約定的遵照依靠自覺.
可是註釋法也有優勢:易於實現,不須要額外的類或者函數,能夠提升代碼的重用性,由於程序員能夠把實現一樣接口的類互換使用.
這種方法更嚴謹一些,類明確聲明本身實現了哪些接口,那些與類打交道的對象能夠針對這些聲明進行檢查,那些接口自身仍然只是註釋,可是如今你能夠經過檢查一個屬性得知某個類自稱實現了什麼接口:
/* interface Composite { function add(child); function remove(child); function getChild(index); } interface FormItem { function save(); } */ var CompositeForm = function (id, method, action) { this.implementsInterfaces = ['Composite', 'FormItem']; ... }; ... function addForm (formInstance) { if(!implements(formInstance, 'Composite', 'FormItem')) { throw new Error("Object does not implement a required interface."); } ... } //The implements function, which checks to see if an object declares that it //implements the required interfaces. function implements (object) { for (var i = 1; i < arguments.length; i++) { //Loop through all arguments after the first one. var interfaceName = arguments[i]; var interfaceFound = false; for (var j = 0; j < object.implementsInterface.length; j++) { if (object.implementsInterfaces[j] == interfaceName) { interfaceFound = true; break; } } if (!interfaceFound) { return false; //An interface was not found. } } return true; //All interfaces were found. }
在這個例子中,CompositeForm宣稱本身實現了Composite和FormItem接口,把接口名稱加入一個implementsInterfaces數組,類顯示聲明本身支持什麼接口,任何一個要求其參數屬於特定類型的函數均可以對這個屬性進行檢查,並在所需接口未在聲明之列時拋出錯誤.
優勢:對類所實現的接口提供了文檔說明,若是所須要的接口不在一個類宣稱支持的接口之列,會報錯.
缺點:未確保真正實現了自稱實現的接口,只知道它是否說本身實現了接口.在建立一個類時聲明它實現了一個接口,可是後來在實現該接口所規定的方法時卻漏掉了其中的某一個,這一種錯誤很常見,此時全部檢查都能經過,可是那個方法卻不存在,是一個隱患.
類是否聲明本身支持哪些接口並不重要,只要他具備這些接口中的方法就行.James Whitcomb Riley曾說過'像鴨子同樣走路而且嘎嘎叫的就是鴨子',也就是說鴨式辨型法把對象實現的方法集做爲判斷他是否是某個類的實例的惟一標準,在檢查一個類是否實現了某個接口時特別有用.若是對象具備與接口定義的方法同名的全部方法,那麼就能夠認爲它實現了這個接口,用一個輔助函數來確保對象具備所必需的方法:
//Interface. var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); var FormItem = new Interface('FormItem', ['save']); //CompositeForm class var CompositeForm = function (id, method, action) { ... }; ... function addForm (formInstance) { ensureImplements(formInstance, Composite, FormItem); //This function will throw an error if //a required method is not implemented. ... }
與另外兩種方法不一樣,這種方法並不藉助於註釋.ensureImplements函數須要至少兩個參數,其一是想要檢查的對象,其他參數是對那個對象進行檢查的接口.該函數檢查其第一個參數所表明的對象是否實現了那些接口所聲明的全部方法,若是發現漏掉任何一個方法,就會拋出錯誤,其中包含所缺乏的那個方法和未被正確實現的接口的名稱.這種檢查可用於代碼中任何須要確保某個對象實現某個接口的地方.
儘管鴨式辨型可能最有用,可是類並不聲明本身實現了哪些接口,下降了代碼的重用性,並且也缺少其餘兩種方法的自我描述性,他須要一個輔助類Interface和一個輔助函數ensureImplements,只關心方法的名稱,並不檢查其參數的名稱,數目,類型.
綜合使用了第一種和第三種,用註釋聲明類支持的接口,從而提升代碼重用性及其文檔完善性.採用輔助類Interface和類方法Interface.ensureImplements來對對象實現的方法進行顯式檢查.示例:
//Interface. var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); var FormItem = new Interface('FormItem', ['save']); //CompositeForm class var CompositeForm = fucntion(id, method, action) {//implements Composite //, FormItem ... }; ... function addForm (formInstance) { Interface.ensureImplements(formInstance, Composite, FormItem); //This function will throw an error if a required method //is not implemented, halting execution of the function. //All code beneath this line will be executed only if the checks pass. ... }
若是Interface.ensureImplements發現有問題,就會拋出一個錯誤,要麼被其餘代碼捕捉到並獲得處理要麼中斷程序的執行.
下面是書中使用的Interface類定義:
//Constructor. var Interface = function(name, methods) { if(arguments.length != 2){ throw new Error("Interface constructor called with " + arguments.length + "arguments, but expected exactly 2."); } this.name = name; this.methods = []; for(var i = 0; len = methods.length; i < len; i++) { if(typeof methods[i] !== 'string') { throw new Error("Interface constructor expects method names to be " + "passed in as a string."); } this.methods.push(methods[i]); } }; //Static class method. Interface.ensureImplements = function (object) { if (arguments.length < 2) { throw new Error("Function Interface.ensureImplements called with " + arguments.length +"arguments, but expected at least 2."); } for (var i = 1, len = arguments.length; i < len; i++) { var interface = arguments[i]; if(interface.constructor !== interface) { throw new Error("Function Interface.ensureImplements expects arguments" + "two and above to be instance of Interface."); } for (var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) { var method = interface.methods[j]; if(!object[method] || typeof object[method] !== 'function'){ throw new Error("Function Interface.ensureImplements: object " + "does not implement the " + interface.name + " interface.Method " + method + " was not found."); } } } };
能夠看出,該類的全部方法其參數都有嚴格要求,若是參數未經過檢查將拋出錯誤,咱們特意加入這種檢查的目的在於:若是沒有拋出錯誤,那麼你能夠確定接口已經獲得了正確的聲明和實現.
說實話,以前我還在想好像真的就沒怎麼用過所謂的接口,或許框架源碼涉及得較多其餘地方涉及地較少,模塊化也沒怎麼用過接口.下面來談談接口究竟是怎麼真正派上用場的.
許多js程序員根本不用接口或者它提供的那種檢查,只能說接口在運用設計模式實現複雜系統的時候最能體現價值,看似下降了js的靈活性,實際上由於使用接口能夠下降對象間耦合程度因此提升了代碼靈活性.可讓函數變得更靈活,由於既能向函數傳遞任何類型的參數還能保證它只會使用那些具備必要方法的對象.
在大型項目中,接口相當重要,經常須要使用還未編寫出來的API或者須要提供一些佔位代碼stub以避免延誤開發進度,接口在這種場合中的重要性表如今許多方面,它們記載着API,能夠做爲程序員正式交流的工具,在佔位代碼中被替換爲最終的API時就能馬上知道所需方法是否獲得實現.在開發過程當中若是API發生變化,只要新的API實現一樣的接口,它就能完美無缺地替換原有API.
如今,項目中用到來自Internet,沒法直接控制的代碼愈來愈廣泛,部署在外部環境中的程序庫/搜索,email,map等服務的API就是這類代碼的例子.即便來源可信,也必須謹慎使用,確保其變化不會在本身的代碼中引發問題.一種應對之策就是爲所依賴的每個API建立一個Interface對象,而後對接收到的每個對象都進行檢查,以確保其正確實現了那些接口:
var DynamicMap = new Interface('DynamicMap', ['centerOnPoint', 'zoom', 'draw']); function displayRoute (mapInstance) { Interface.ensureImplements(mapInstance, DynamicMap); mapInstance.centerOnPoint(12, 34); mapInstance.zoom(5); mapInstance.draw(); ... }
示例中,displayRoute函數要求傳入的參數具備3個特定方法,經過使用一個Interface對象和調用Interface.ensureImplements方法,能夠確保這些方法已經獲得實現,不然將會見到錯誤.這個錯誤能夠用try/catch捕獲,而後可能會被用於發送一條ajax請求,將外部API引發的問題告知用戶.
判斷在代碼中使用接口是否划算最重要,也最困難.對於小型項目來講接口的好處也許並不明顯只是徒增複雜度.自行權衡利弊後,若是認爲利大於弊,須要在項目中使用接口,能夠按以下方法:
將Interface類--Interface.js文件引入html文件.
逐一檢查代碼中全部以對象爲參數的方法,搞清要求這些對象參數具備哪些方法.
爲每個不一樣的方法建立一個Interface對象.
剔除全部針對構造器的顯式檢查,由於鴨式辨型的使用因此對象類型再也不重要.
以Interface.ensureImplements取代原來的構造器檢查.
按以上方法使用接口以後的好處:代碼的耦合度下降,再也不依賴於任何特定的類實例,而是檢查所須要的特性是否都已就緒(無論具體如何實現),因而你在對代碼進行優化和重構時將擁有更大的自由.
工廠模式,對象工廠所建立的具體對象會因具體狀況,使用接口能夠確保所建立的這些對象能夠互換使用.對象工廠能夠保證其生產出來的對象都生產了必需的方法.
組合模式,其中心思想在於能夠將對象羣體與其組成對象同等對待,經過讓他們實現一樣的接口來作到,若是不進行某種形式的鴨式辨型或者類型檢查,組合模式就會失去做用.
裝飾者模式,經過透明地爲另外一對象而發揮做用,這是經過實現與另外那個對象徹底相同的接口而作到的,對於外界而言,一個裝飾者和他所包裝的對象看出區別,使用Interface類來確保所建立的裝飾者對象實現了必須的方法.
命令模式,代碼中全部的命令對象都要實現同一批方法(execute,run,undo等等),經過使用接口,爲執行這些命令對象而建立的類能夠沒必要知道這些對象具體是什麼,只要知道他們實現了正確的接口便可.因而你能夠建立出模塊化度高而耦合度低的用戶界面和API.
我google了一下,有關接口模式的開發實踐並非不少,可是就算實際小型項目當中用的很少,接口模式依然仍是許多其餘模式的基礎依賴,地位重要.
最後分享一個蠻有用的工具,上git的好東西.