摘要:前言這一系列的文章將主要基于設(shè)計(jì)模式這本書的要點(diǎn)還有一些翻閱的博客文章借鑒來(lái)源會(huì)注明外加自己的一些與直覺不同于其他設(shè)計(jì)模式類的書設(shè)計(jì)模式是一本講述設(shè)計(jì)模式在動(dòng)態(tài)語(yǔ)言中的實(shí)現(xiàn)的書它從設(shè)計(jì)的角度教人編寫代碼書中的許多實(shí)例代碼來(lái)自實(shí)戰(zhàn)項(xiàng)目對(duì)面向?qū)?/p>
前言
這一系列的文章將主要基于js設(shè)計(jì)模式這本書的要點(diǎn)還有一些翻閱的博客文章,借鑒來(lái)源會(huì)注明,外加自己的一些demo與直覺.不同于其他設(shè)計(jì)模式類的書,js設(shè)計(jì)模式是一本講述設(shè)計(jì)模式在動(dòng)態(tài)語(yǔ)言js中的實(shí)現(xiàn)的書它從設(shè)計(jì)的角度教人編寫代碼,書中的許多實(shí)例代碼來(lái)自YUI實(shí)戰(zhàn)項(xiàng)目,對(duì)js面向?qū)ο蟮奶匦躁U述到位,詳細(xì)剖析了面向?qū)ο蟮讓訉?shí)現(xiàn)機(jī)制.隨著ajax技術(shù)的興起,Web應(yīng)用的許多業(yè)務(wù)邏輯從服務(wù)器轉(zhuǎn)移到瀏覽器端執(zhí)行,,js越來(lái)越大的規(guī)模和復(fù)雜程度需要fe rd更好地運(yùn)用面向?qū)ο筇匦?js設(shè)計(jì)模式 和 ajax設(shè)計(jì)模式 做個(gè)比較:后者研究了運(yùn)用ajax設(shè)計(jì)模式開發(fā)Web應(yīng)用的各種設(shè)計(jì)模式,前者研究了通用面向?qū)ο笤O(shè)計(jì)模式在js中的實(shí)現(xiàn),不僅僅用于瀏覽器端編程,還可以有其他環(huán)境下的實(shí)現(xiàn).
本書比較大的缺點(diǎn)在于作者闡述對(duì)各種設(shè)計(jì)模式的實(shí)現(xiàn)時(shí)沒有選擇js面向?qū)ο缶幊套钭匀坏睦^承實(shí)現(xiàn)方式原型繼承,而是選擇了該書出版時(shí)廣大讀者較為熟悉的類繼承.
設(shè)計(jì)模式運(yùn)用在java,cpp等等各類程序語(yǔ)言上已經(jīng)有許多年了,應(yīng)用于各種語(yǔ)言和語(yǔ)法上表現(xiàn)出一致性,基本結(jié)構(gòu)是相同的,只是細(xì)節(jié)上略有差別,但對(duì)js則不一樣,那些常見的面向?qū)ο筇匦赃€需要靠晦澀的技巧來(lái)實(shí)現(xiàn),所以設(shè)計(jì)模式也同樣需要這樣的手段.
這本書大體上分為兩部分,第一部分給出實(shí)現(xiàn)具體設(shè)計(jì)模式所需要的面向?qū)ο筇匦缘幕A(chǔ)知識(shí),主要包括接口,封裝,信息隱藏,繼承,單體模式等等.第二部分專注于各種具體的設(shè)計(jì)模式及其在js中的應(yīng)用,主要介紹了工廠模式,橋接模式,組合模式,門面模式...幾種常見的模式.
第一部分第一部分前6章是js面向?qū)ο蟮幕A(chǔ)知識(shí),第1章富有表現(xiàn)力的js主要介紹js實(shí)現(xiàn)同一任務(wù)的編程風(fēng)格多樣性,函數(shù)式編程實(shí)現(xiàn)面向?qū)ο?運(yùn)用設(shè)計(jì)模式的緣由.第2章接口分析了js如何采用最佳特性來(lái)模仿接口,探討了接口檢查的各種方式,然后給出一個(gè)用來(lái)檢查對(duì)象是否具有必要方法的可重用類.第3章封裝和信息隱藏探討了js中創(chuàng)建對(duì)象的各種不同方式,以及如何創(chuàng)建public,private,protected方法.第4章繼承講述了js中如何創(chuàng)建子類,對(duì)比了類繼承和原型繼承及其應(yīng)用場(chǎng)合.第5章單體模式討論了js中的單體模式,命名空間,代碼組織以及分支技術(shù)--根據(jù)運(yùn)行時(shí)環(huán)境動(dòng)態(tài)定義方法.第6章方法的鏈?zhǔn)秸{(diào)用,對(duì)有沒有采用該技術(shù)來(lái)創(chuàng)建一個(gè)js庫(kù)作比較.
第二部分第7章工廠模式,它有助于消除那些彼此實(shí)例化對(duì)方的類之間的耦合,然后改用一個(gè)方法來(lái)確定要實(shí)例化哪個(gè)類.主要討論了兩點(diǎn),一個(gè)是如何用一個(gè)類(通常是一個(gè)單體)來(lái)生成實(shí)例的簡(jiǎn)單工廠模式,一個(gè)是如何用子類來(lái)確定一個(gè)成員對(duì)象應(yīng)該是哪種具體類實(shí)例的工廠模式.第8章橋接模式討論了一種把兩個(gè)對(duì)象連接在一起而且還能避免二者間強(qiáng)耦合的方法.橋接元素允許兩個(gè)對(duì)象獨(dú)立變化,演示了如何用橋接元素把函數(shù)松散得綁定到事件.第9章組合模式非常適合于創(chuàng)建Web動(dòng)態(tài)頁(yè)面,演示了如何達(dá)到只用一條命令即可在許多對(duì)象上激發(fā)復(fù)雜或者遞歸性的行為,以及如何把一系列對(duì)象組織成復(fù)雜的層次體系.第10章門面模式用來(lái)為對(duì)象創(chuàng)建一個(gè)更完善的接口,把現(xiàn)有接口轉(zhuǎn)換為一個(gè)更便于使用的接口.這一章解釋了大多數(shù)js庫(kù)都是為js在具體瀏覽器中的實(shí)現(xiàn)提供的一個(gè)門面,也闡述了如何用這種模式創(chuàng)建便利方法和事件工具庫(kù).第11章適配器模式可以讓現(xiàn)有接口契合實(shí)際需要.適配器也稱包裝器,用來(lái)把不匹配的接口替換為一個(gè)可用于現(xiàn)有系統(tǒng)中的接口.該章探討了如何用適配器彌補(bǔ)js庫(kù)之間的差異并金華一中庫(kù)到另一種庫(kù)之間的過渡過程,分析了一個(gè)電子郵件API并創(chuàng)建了一個(gè)有助于升級(jí)到新版本的適配器.第12章裝飾者模式引入了一種可以為對(duì)象添加屬性而不必創(chuàng)建新的子類的方法.用于把對(duì)象透明地包裝到另一種具有相同接口的對(duì)象中.還研究了裝飾者的結(jié)構(gòu)以及如何將其與工廠模式結(jié)合以自動(dòng)創(chuàng)建內(nèi)嵌對(duì)象,該章有一個(gè)性能分析器示例,示范如何用裝飾者模式動(dòng)態(tài)實(shí)現(xiàn)接口.第13章享元模式用于優(yōu)化,該章示范了如何通過把打屁獨(dú)立對(duì)象轉(zhuǎn)變?yōu)樯倭抗蚕韺?duì)象以大幅削減實(shí)現(xiàn)應(yīng)用軟件所需的對(duì)象數(shù)目,該章還創(chuàng)建了一個(gè)Web日歷和一個(gè)可重用的工具提示類,示范如何按照享元模式進(jìn)行重構(gòu).第14章代理模式用于控制對(duì)象的訪問代理.該章研究了如何為本體real object創(chuàng)建一個(gè)代理對(duì)象以作為替身,并使其能被遠(yuǎn)程訪問,還探討了該模式的種種用法.第15章觀察者模式對(duì)對(duì)象的狀態(tài)進(jìn)行觀察,發(fā)生變化時(shí)能得到通知.也稱為發(fā)布者-訂閱者publisher-subscriber模式,用于讓對(duì)象對(duì)事件進(jìn)行監(jiān)聽以便對(duì)其作出響應(yīng),該章討論了在使用一個(gè)動(dòng)畫庫(kù)時(shí)可以訂閱的各種事件.第16章命令模式對(duì)方法調(diào)用進(jìn)行封裝:進(jìn)行參數(shù)化和傳遞,然后在需要的時(shí)候再加以執(zhí)行.應(yīng)用場(chǎng)合主要包括創(chuàng)建創(chuàng)建用戶界面--尤其是那種需要不受限制的取消操作的用戶界面.該章還討論了該模式的結(jié)構(gòu).第17章職責(zé)蓮模式用來(lái)消除請(qǐng)求的發(fā)送者和接受者之間的耦合,研究了如何使用該模式處理事件的捕獲和冒泡,如何創(chuàng)建弱耦合模塊和優(yōu)化事件監(jiān)聽器的綁定.
簡(jiǎn)易函數(shù)說(shuō)明對(duì)于書中的代碼實(shí)例,作者使用了一些簡(jiǎn)易函數(shù)來(lái)執(zhí)行安裝事件監(jiān)聽器,派生子類,處理Cookie,引用HTML元素等任務(wù),沒有使用YUI或者jQuery等js庫(kù)來(lái)提供類似功能,讀者可以使用自己喜歡的任何庫(kù)結(jié)合使用.下面是這些簡(jiǎn)易函數(shù)的說(shuō)明:
$(id) 根據(jù)ID獲取HTML元素的引用,其參數(shù)可以是字符串,字符串的數(shù)組等等
addEvent(obj, type, func), 把函數(shù)fund作為元素obj的事件監(jiān)聽器,type表示該函數(shù)監(jiān)聽的事件.
addLoadEvent(func),將函數(shù)fund關(guān)聯(lián)到window對(duì)象的load事件.
getElementByClass(searchClass, node, tag),獲取所有class屬性值為searchClass的元素的引用.node和tag這兩個(gè)參數(shù)可有可無(wú),可以用來(lái)縮小搜索范圍,函數(shù)的返回值是一個(gè)數(shù)組.
insertAfter(parent, node, referenceNode),插入元素node,其父元素為parent,其位置在referenceNode之后.
getCookie(name),獲取與名為name的Cookie相關(guān)聯(lián)的字符串.
setCookie(name, value, expires, path, domain, secure),把與名為name的Cookie相關(guān)聯(lián)的字符串設(shè)置為value,除name和value外的其它參數(shù)均可有可無(wú).
deleteCookie(name),將名為name的Cookie的過期時(shí)間設(shè)置到過去,即刪除這個(gè)Cookie.
clone(object),創(chuàng)建object的一個(gè)副本,用于原型繼承,見第4章.
extend(subClass, superClass),執(zhí)行一些必要的工作,使SubClass成為superClass的子類,見第4章.
augment(receivingClass, givingClass),將givingClass中的方法輸入receivingClass中,見第4章.
第一章 js靈活性js編程風(fēng)格多樣化,既可以函數(shù)式編程,也可以面向?qū)ο笫?可以模仿其他語(yǔ)言的編程模式和慣用法.以啟動(dòng)和停止動(dòng)畫為例,定義一個(gè)類,用來(lái)創(chuàng)建可以保存狀態(tài)并且具有一些僅對(duì)其內(nèi)部狀態(tài)進(jìn)行操作的方法的動(dòng)畫對(duì)象:
//Anim Class var Anim = function () { ... }; Anim.prototype.start = function () { ... }; Anim.protype.stop = function () { ... } //Usage var myAnim = new Anim(); myAnim.start(); ... myAnim.stop();
上面的代碼定義了一個(gè)名為Anim的類,并把兩個(gè)方法賦給該類的prototype屬性.也可以更便捷地把類的定義封裝在一條聲明里面:
//Anim class, with a slightly different syntax for declaring methods. var Anim = function() { ... } Anim.prototype = { start: function () { ... }, stop: function () { ... } };
換種風(fēng)格:
//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用于為類添加新方法, 它有兩個(gè)參數(shù), 第一個(gè)是字符串, 表示新方法的名稱; 第二個(gè)是用作新方法的函數(shù). 可以修改一下Function.Prototype.method, 使其可以被鏈?zhǔn)秸{(diào)用, 這只需要返回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 () { ... });
以上是完成同一項(xiàng)任務(wù)的不同方法, 他們的風(fēng)格略有差異, 不同風(fēng)格在代碼篇幅, 編碼效率, 和執(zhí)行性能方面各有特點(diǎn), 不同人偏好不同的風(fēng)格.
弱類型語(yǔ)言在js中, 定義變量時(shí)不必聲明其類型. 但是變量依然具有類型, 一個(gè)變量具有的類型取決于其包含的數(shù)據(jù). 有三種原始類型: 布爾型, 數(shù)值型(不同于其他語(yǔ)言, js不區(qū)分整數(shù)和浮點(diǎn)數(shù))和字符串型. 另外還有對(duì)象類型和包含可執(zhí)行代碼的函數(shù)類型, 前者是一種復(fù)合數(shù)據(jù)類型(數(shù)組是一種特殊的對(duì)象, 包含著一批值的有序集合),最后還有null空類型和undefined.原始類型按值傳遞,其他類型按引用傳遞.
js中的變量既可以根據(jù)所賦的值改變類型,還可以進(jìn)行原始類型之間的轉(zhuǎn)換.toString可以把數(shù)值或者布爾值轉(zhuǎn)換為字符串.parseFloat和parseInt可以把字符串轉(zhuǎn)換為數(shù)值,雙重非可以把字符串或者數(shù)值轉(zhuǎn)換為布爾值.
js這樣的弱類型語(yǔ)言變量帶來(lái)了極大的靈活性,但是會(huì)根據(jù)需要進(jìn)行轉(zhuǎn)換,一般說(shuō)來(lái)不會(huì)有類型錯(cuò)誤.
js中的函數(shù)可以存儲(chǔ)在變量中,可以作為參數(shù)傳給其他函數(shù),可以做為返回值從其然函數(shù)傳出,還可以在運(yùn)行時(shí)進(jìn)行構(gòu)造.這些特性帶來(lái)了極大的靈活性和表達(dá)能力.
可以用function () {...}創(chuàng)建匿名函數(shù),沒有函數(shù)名但是可以賦值給變量.
//An anonymous function, executed immediately. (function () { var foo = 10; var bar = 2; console.log(foo * bar); })();
該函數(shù)在定義之后立即執(zhí)行,甚至不用賦值給一個(gè)變量,出現(xiàn)在函數(shù)聲明之后的一對(duì)括號(hào)立即對(duì)函數(shù)進(jìn)行調(diào)用,實(shí)際上括號(hào)當(dāng)中也可以有參數(shù):
//An anonymous function with arguments. (function (foo, bar) { console.log(foo * bar); )(10, 2); ` 這個(gè)匿名函數(shù)與前一個(gè)等價(jià),只不過變量沒有在函數(shù)內(nèi)部用var聲明,而是作為參數(shù)從外部傳入,函數(shù)也可以返回值,然后賦值給一個(gè)變量: `//An anonymous function returns a value. var baz = (funciton(foo, bar) { return foo * bar; })(10, 2); //baz equals to 20.
匿名函數(shù)最好的用途是用來(lái)創(chuàng)建閉包,closure是一個(gè)受到保護(hù)的變量空間,由內(nèi)嵌函數(shù)生成,js有函數(shù)級(jí)的作用域:定義在函數(shù)內(nèi)部的變量在函數(shù)外部不能被訪問.js的作用域又是詞法性質(zhì)的,函數(shù)運(yùn)行在定義它的作用域中,而不是在調(diào)用它的作用域中,把這兩個(gè)因素結(jié)合起來(lái)就能通過把變量包裹在匿名函數(shù)中加以保護(hù).可以這樣創(chuàng)建類的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定義在匿名函數(shù)中,因?yàn)楹瘮?shù)baz定義在這個(gè)比保重,所以它能訪問兩個(gè)變量,即使是在該閉包執(zhí)行結(jié)束后.
對(duì)象易變在js中,除了布爾,數(shù)值,字符串三種原始數(shù)據(jù)類型(即便是他們也可以進(jìn)行包裝為對(duì)象),一切都是易變的對(duì)象:為函數(shù)添加屬性等等.
function displayError (message) { displayError.numTimesExecuted++; console.log(message); }; displayError.numTimesExecuted = 0;
你也可以對(duì)先前定義的類和實(shí)例化的對(duì)象進(jìn)行修改:
//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()); };
這個(gè)例子中,類的getGreeting方法是在已經(jīng)創(chuàng)建了類的兩個(gè)實(shí)例之后才添加的,但這兩個(gè)實(shí)例仍然能夠獲得這個(gè)方法,其原因在于prototype對(duì)象的工作機(jī)制.對(duì)象alice得到了displayGreeting方法,其他事里卻沒有.
與對(duì)象易變性相關(guān)的還有內(nèi)省introspection:在運(yùn)行時(shí)檢查所具有的屬性和方法,還可以使用這種信息動(dòng)態(tài)實(shí)例化類和執(zhí)行其方法(反射reflection),甚至不需要在開發(fā)時(shí)知道他們的名稱.書中js大多數(shù)用來(lái)模仿傳統(tǒng)面向?qū)ο筇匦缘募夹g(shù)都依賴于對(duì)象的易變性和反射.在C++,Java不能對(duì)已經(jīng)實(shí)例化的對(duì)象進(jìn)行擴(kuò)展,不能對(duì)已經(jīng)定義好的類進(jìn)行修改,而在js中,任何東西都可以在運(yùn)行時(shí)修改,當(dāng)然也有弊端:可以定義一個(gè)具有一套方法的類,卻無(wú)法確定這些方法會(huì)一直保持不變,這是js很少進(jìn)行類型檢查的原因.
js使用的是基于對(duì)象--原型繼承,可以用來(lái)模仿類繼承,編碼時(shí)應(yīng)根據(jù)手頭任務(wù)的實(shí)際情況和兩種繼承的實(shí)際性能表現(xiàn)進(jìn)行選擇.
js中的設(shè)計(jì)模式1995年GoF出版的"設(shè)計(jì)模式"整理記錄了對(duì)象間相互作用的各種方式,用以創(chuàng)建不同類型對(duì)象的套路被稱為design pattern.
js強(qiáng)大的表現(xiàn)力賦予了程序員在運(yùn)用設(shè)計(jì)模式編碼時(shí)的靈活性,js中使用設(shè)計(jì)模式的主要原因如下:
可維護(hù)性,有助于降低模塊間的耦合強(qiáng)度,使得重構(gòu),更換模塊,大型團(tuán)隊(duì)中的合作變得更容易.
溝通,為處理不同類型的對(duì)象提供了一套通用的術(shù)語(yǔ),比如該系統(tǒng)它使用了工廠模式,可以在較高層面上對(duì)帶有特定模式的名稱進(jìn)行討論,不必涉及過多的細(xì)節(jié).
性能,他們起優(yōu)化作用,可以大幅提高程序的運(yùn)行速度,減少需要傳送到客戶端的代碼量,最經(jīng)典的享元模式和代理模式.
你也可能不使用因?yàn)槿缦戮売蒬esign pattern:
復(fù)雜性,獲得可維護(hù)性往往需要付出代價(jià),新手更不容易理解.
性能,多數(shù)模式性能上都有一點(diǎn)點(diǎn)拖累,不過能不能接受取決于項(xiàng)目需要.
懂得應(yīng)該在什么時(shí)候選擇恰當(dāng)?shù)哪J捷^為困難,盲目套用會(huì)帶來(lái)許多不安全問題.
小節(jié)這一章是基礎(chǔ)概念,研究意義,研究?jī)?nèi)容等等方面的定位與確認(rèn),并不太適合作出相關(guān)實(shí)踐性的項(xiàng)目或者舉出demo.
第二章 接口js中沒有內(nèi)置的創(chuàng)建或者實(shí)現(xiàn)接口的方法,也沒有內(nèi)置的方法用于判斷一個(gè)對(duì)象是否實(shí)現(xiàn)了與另一個(gè)對(duì)象相同的一套方法,使得對(duì)象很難互換使用,不過js很靈活,添加這些特性并不難.
接口定義它提供了一種用以說(shuō)明一個(gè)對(duì)象具有哪些方法的手段,可以表明這些方法的語(yǔ)義,但是并不解釋應(yīng)該如何實(shí)現(xiàn). e.g.如果一個(gè)接口包含setName方法,那么可以認(rèn)為該方法的實(shí)現(xiàn)有一個(gè)字符串參數(shù),并且會(huì)把該參數(shù)賦值給name變量.
有了接口,就能按照對(duì)象的特性進(jìn)行分組,即使一批對(duì)象存在極大差異,只要他們都實(shí)現(xiàn)Comparable接口,那么在Object.compare(anotherObject)方法中就可以互換使用這些對(duì)象.
描述作用,促進(jìn)代碼重用.表明一個(gè)類實(shí)現(xiàn)了哪些方法,從而幫助使用該類.
如果事先知道了接口,就能減少在集成兩個(gè)對(duì)象的過程中出現(xiàn)的問題,可以事先聲明一個(gè)類應(yīng)該具有哪些特性和操作,程序員A針對(duì)所需要的類定義一個(gè)接口,然后把它交給B,B可以隨意編碼代碼只要他定義的類實(shí)現(xiàn)了那個(gè)接口.
測(cè)試和調(diào)試更為輕松.使用接口可以讓類型不匹配錯(cuò)誤的查找變得更容易,如果一個(gè)對(duì)象沒有實(shí)現(xiàn)必要的方法,就會(huì)得到明確的錯(cuò)誤提示,邏輯錯(cuò)誤可以被限制在方法自身,而不是在對(duì)象的構(gòu)成之中.接口還能讓代碼更穩(wěn)固,因?yàn)閷?duì)接口的任何改變?cè)谒袑?shí)現(xiàn)它的類中都必須體現(xiàn)出來(lái),如果接口添加一個(gè)操作,某個(gè)實(shí)現(xiàn)它的類并沒有相應(yīng)的添加這個(gè)操作,就會(huì)報(bào)錯(cuò).
缺點(diǎn)接口的使用在一定程度上強(qiáng)化了類型的作用,減少了js的靈活性.
js中任何實(shí)現(xiàn)接口的方法都會(huì)有性能上的一些影響,緣由額外的方法調(diào)用的開銷,實(shí)現(xiàn)方法中使用兩個(gè)for循環(huán)來(lái)遍歷每個(gè)接口的每個(gè)方法,對(duì)于大型接口和實(shí)現(xiàn)許多不同接口的對(duì)象會(huì)消耗較多時(shí)間.
接口的最大問題是沒法強(qiáng)迫其它人遵循你定義的接口,其他語(yǔ)言中接口內(nèi)置,某人定義了實(shí)現(xiàn)一個(gè)接口的類,那么編譯器會(huì)確保該類的確實(shí)現(xiàn)了這個(gè)接口,在js中必須手動(dòng)地去保證某個(gè)類實(shí)現(xiàn)了一個(gè)接口,編碼規(guī)范和輔助類可以幫忙但無(wú)法根治.只有團(tuán)隊(duì)成員都愿意使用接口并進(jìn)行檢查,接口的很多價(jià)值才能體現(xiàn)出來(lái).
其他面向?qū)ο笳Z(yǔ)言處理接口的方式以Java為例,下面是java.io包中的一個(gè)接口:
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; ... }
它列出了一個(gè)類應(yīng)該實(shí)現(xiàn)的一批方法,包括方法的參數(shù)和可能會(huì)拋出的異常,每一行都像是一個(gè)方法聲明,只不過是以一個(gè)分號(hào)而不是一對(duì)大括號(hào)結(jié)尾.
創(chuàng)建一個(gè)實(shí)現(xiàn)該接口的類需要使用關(guān)鍵字implements:
public class DataOutStream extends FilterOutputStream implements DataOutput { public final void writeBoolean (blooean value) throws IOException { write (value ? 1 : 0); } ... }
該類聲明并具體實(shí)現(xiàn)了接口中列出的每一個(gè)方法,漏掉任何一個(gè)方法都會(huì)導(dǎo)致在編譯時(shí)顯示錯(cuò)誤.下面是Java編譯器在發(fā)現(xiàn)一個(gè)接口錯(cuò)誤時(shí)可能產(chǎn)生的輸出信息:
MyClass should be declared abstract; it does not define writeBoolean(boolean) in MyClass.
從上面的Java代碼可以看出接口包含的信息:需要實(shí)現(xiàn)的方法名以及這些方法的參數(shù),類的定義明確地聲明他們實(shí)現(xiàn)了這個(gè)接口(通常使用implements關(guān)鍵字),一個(gè)類可以實(shí)現(xiàn)不止一個(gè)接口,如果接口中某個(gè)方法沒有被實(shí)現(xiàn),則會(huì)產(chǎn)生一個(gè)錯(cuò)誤有的語(yǔ)言產(chǎn)生在編譯時(shí),有的在運(yùn)行時(shí),錯(cuò)誤包含三類信息:類名,接口名,未被實(shí)現(xiàn)的方法名.
因?yàn)閖s中沒有interface和implements關(guān)鍵字(es5的嚴(yán)格模式把interface視作保留字),不能在運(yùn)行時(shí)對(duì)接口約定是否得到遵守進(jìn)行檢查,所以我們可以通過使用輔助類和顯式檢查來(lái)進(jìn)行模仿.
注釋法,屬性檢查法,鴨式辨型法,三種方法當(dāng)中沒有完美的,結(jié)合起來(lái)可以令人滿意.
用注釋描述接口最簡(jiǎn)單,但是效果最差,使用interface和implements關(guān)鍵字,但是放在注釋當(dāng)中,以免引起語(yǔ)法錯(cuò)誤:
/* 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實(shí)現(xiàn)了所有方法而進(jìn)行檢查,不拋出錯(cuò)誤,對(duì)測(cè)試和調(diào)試沒有任何幫助.歸屬于程序文檔范疇,對(duì)接口約定的遵守依靠自覺.
但是注釋法也有優(yōu)點(diǎn):易于實(shí)現(xiàn),不需要額外的類或者函數(shù),可以提高代碼的重用性,因?yàn)槌绦騿T可以把實(shí)現(xiàn)同樣接口的類互換使用.
這種方法更嚴(yán)謹(jǐn)一些,類明確聲明自己實(shí)現(xiàn)了哪些接口,那些與類打交道的對(duì)象可以針對(duì)這些聲明進(jìn)行檢查,那些接口自身仍然只是注釋,但是現(xiàn)在你可以通過檢查一個(gè)屬性得知某個(gè)類自稱實(shí)現(xiàn)了什么接口:
/* 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. }
在這個(gè)例子中,CompositeForm宣稱自己實(shí)現(xiàn)了Composite和FormItem接口,把接口名稱加入一個(gè)implementsInterfaces數(shù)組,類顯示聲明自己支持什么接口,任何一個(gè)要求其參數(shù)屬于特定類型的函數(shù)都可以對(duì)這個(gè)屬性進(jìn)行檢查,并在所需接口未在聲明之列時(shí)拋出錯(cuò)誤.
優(yōu)點(diǎn):對(duì)類所實(shí)現(xiàn)的接口提供了文檔說(shuō)明,如果所需要的接口不在一個(gè)類宣稱支持的接口之列,會(huì)報(bào)錯(cuò).
缺點(diǎn):未確保真正實(shí)現(xiàn)了自稱實(shí)現(xiàn)的接口,只知道它是否說(shuō)自己實(shí)現(xiàn)了接口.在創(chuàng)建一個(gè)類時(shí)聲明它實(shí)現(xiàn)了一個(gè)接口,但是后來(lái)在實(shí)現(xiàn)該接口所規(guī)定的方法時(shí)卻漏掉了其中的某一個(gè),這一種錯(cuò)誤很常見,此時(shí)所有檢查都能通過,但是那個(gè)方法卻不存在,是一個(gè)隱患.
類是否聲明自己支持哪些接口并不重要,只要他具有這些接口中的方法就行.James Whitcomb Riley曾說(shuō)過"像鴨子一樣走路并且嘎嘎叫的就是鴨子",也就是說(shuō)鴨式辨型法把對(duì)象實(shí)現(xiàn)的方法集作為判斷他是不是某個(gè)類的實(shí)例的唯一標(biāo)準(zhǔn),在檢查一個(gè)類是否實(shí)現(xiàn)了某個(gè)接口時(shí)特別有用.如果對(duì)象具有與接口定義的方法同名的所有方法,那么就可以認(rèn)為它實(shí)現(xiàn)了這個(gè)接口,用一個(gè)輔助函數(shù)來(lái)確保對(duì)象具有所必需的方法:
//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函數(shù)需要至少兩個(gè)參數(shù),其一是想要檢查的對(duì)象,其余參數(shù)是對(duì)那個(gè)對(duì)象進(jìn)行檢查的接口.該函數(shù)檢查其第一個(gè)參數(shù)所代表的對(duì)象是否實(shí)現(xiàn)了那些接口所聲明的所有方法,如果發(fā)現(xiàn)漏掉任何一個(gè)方法,就會(huì)拋出錯(cuò)誤,其中包含所缺少的那個(gè)方法和未被正確實(shí)現(xiàn)的接口的名稱.這種檢查可用于代碼中任何需要確保某個(gè)對(duì)象實(shí)現(xiàn)某個(gè)接口的地方.
盡管鴨式辨型可能最有用,但是類并不聲明自己實(shí)現(xiàn)了哪些接口,降低了代碼的重用性,而且也缺乏其他兩種方法的自我描述性,他需要一個(gè)輔助類Interface和一個(gè)輔助函數(shù)ensureImplements,只關(guān)心方法的名稱,并不檢查其參數(shù)的名稱,數(shù)目,類型.
綜合使用了第一種和第三種,用注釋聲明類支持的接口,從而提高代碼重用性及其文檔完善性.采用輔助類Interface和類方法Interface.ensureImplements來(lái)對(duì)對(duì)象實(shí)現(xiàn)的方法進(jìn)行顯式檢查.示例:
//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發(fā)現(xiàn)有問題,就會(huì)拋出一個(gè)錯(cuò)誤,要么被其他代碼捕捉到并得到處理要么中斷程序的執(zhí)行.
Interface類下面是書中使用的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."); } } } };
可以看出,該類的所有方法其參數(shù)都有嚴(yán)格要求,如果參數(shù)未通過檢查將拋出錯(cuò)誤,我們特地加入這種檢查的目的在于:如果沒有拋出錯(cuò)誤,那么你可以肯定接口已經(jīng)得到了正確的聲明和實(shí)現(xiàn).
Interface類的使用場(chǎng)合說(shuō)實(shí)話,之前我還在想好像真的就沒怎么用過所謂的接口,或許框架源碼涉及得較多其他地方涉及地較少,模塊化也沒怎么用過接口.下面來(lái)談?wù)劷涌诘降资窃趺凑嬲缮嫌脠?chǎng)的.
許多js程序員根本不用接口或者它提供的那種檢查,只能說(shuō)接口在運(yùn)用設(shè)計(jì)模式實(shí)現(xiàn)復(fù)雜系統(tǒng)的時(shí)候最能體現(xiàn)價(jià)值,看似降低了js的靈活性,實(shí)際上因?yàn)槭褂媒涌诳梢越档蛯?duì)象間耦合程度所以提高了代碼靈活性.可以讓函數(shù)變得更靈活,因?yàn)榧饶芟蚝瘮?shù)傳遞任何類型的參數(shù)還能保證它只會(huì)使用那些具有必要方法的對(duì)象.
在大型項(xiàng)目中,接口至關(guān)重要,常常需要使用還未編寫出來(lái)的API或者需要提供一些占位代碼stub以免延誤開發(fā)進(jìn)度,接口在這種場(chǎng)合中的重要性表現(xiàn)在許多方面,它們記載著API,可以作為程序員正式交流的工具,在占位代碼中被替換為最終的API時(shí)就能立刻知道所需方法是否得到實(shí)現(xiàn).在開發(fā)過程中如果API發(fā)生變化,只要新的API實(shí)現(xiàn)同樣的接口,它就能天衣無(wú)縫地替換原有API.
現(xiàn)在,項(xiàng)目中用到來(lái)自Internet,無(wú)法直接控制的代碼越來(lái)越普遍,部署在外部環(huán)境中的程序庫(kù)/搜索,email,map等服務(wù)的API就是這類代碼的例子.即使來(lái)源可信,也必須謹(jǐn)慎使用,確保其變化不會(huì)在自己的代碼中引起問題.一種應(yīng)對(duì)之策就是為所依賴的每一個(gè)API創(chuàng)建一個(gè)Interface對(duì)象,然后對(duì)接收到的每一個(gè)對(duì)象都進(jìn)行檢查,以確保其正確實(shí)現(xiàn)了那些接口:
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函數(shù)要求傳入的參數(shù)具有3個(gè)特定方法,通過使用一個(gè)Interface對(duì)象和調(diào)用Interface.ensureImplements方法,可以確保這些方法已經(jīng)得到實(shí)現(xiàn),否則將會(huì)見到錯(cuò)誤.這個(gè)錯(cuò)誤可以用try/catch捕獲,然后可能會(huì)被用于發(fā)送一條ajax請(qǐng)求,將外部API引起的問題告知用戶.
Interface類的用法判斷在代碼中使用接口是否劃算最重要,也最困難.對(duì)于小型項(xiàng)目來(lái)說(shuō)接口的好處也許并不明顯只是徒增復(fù)雜度.自行權(quán)衡利弊后,如果認(rèn)為利大于弊,需要在項(xiàng)目中使用接口,可以按如下方法:
將Interface類--Interface.js文件引入html文件.
逐一檢查代碼中所有以對(duì)象為參數(shù)的方法,搞清要求這些對(duì)象參數(shù)具有哪些方法.
為每一個(gè)不同的方法創(chuàng)建一個(gè)Interface對(duì)象.
剔除所有針對(duì)構(gòu)造器的顯式檢查,因?yàn)轼喪奖嫘偷氖褂盟詫?duì)象類型不再重要.
以Interface.ensureImplements取代原來(lái)的構(gòu)造器檢查.
按以上方法使用接口之后的好處:代碼的耦合度降低,不再依賴于任何特定的類實(shí)例,而是檢查所需要的特性是否都已就緒(不管具體如何實(shí)現(xiàn)),于是你在對(duì)代碼進(jìn)行優(yōu)化和重構(gòu)時(shí)將擁有更大的自由.
工廠模式,對(duì)象工廠所創(chuàng)建的具體對(duì)象會(huì)因具體情況,使用接口可以確保所創(chuàng)建的這些對(duì)象可以互換使用.對(duì)象工廠可以保證其生產(chǎn)出來(lái)的對(duì)象都生產(chǎn)了必需的方法.
組合模式,其中心思想在于可以將對(duì)象群體與其組成對(duì)象同等對(duì)待,通過讓他們實(shí)現(xiàn)同樣的接口來(lái)做到,如果不進(jìn)行某種形式的鴨式辨型或者類型檢查,組合模式就會(huì)失去作用.
裝飾者模式,通過透明地為另一對(duì)象而發(fā)揮作用,這是通過實(shí)現(xiàn)與另外那個(gè)對(duì)象完全相同的接口而做到的,對(duì)于外界而言,一個(gè)裝飾者和他所包裝的對(duì)象看出區(qū)別,使用Interface類來(lái)確保所創(chuàng)建的裝飾者對(duì)象實(shí)現(xiàn)了必須的方法.
命令模式,代碼中所有的命令對(duì)象都要實(shí)現(xiàn)同一批方法(execute,run,undo等等),通過使用接口,為執(zhí)行這些命令對(duì)象而創(chuàng)建的類可以不必知道這些對(duì)象具體是什么,只要知道他們實(shí)現(xiàn)了正確的接口即可.于是你可以創(chuàng)建出模塊化度高而耦合度低的用戶界面和API.
小結(jié)我google了一下,有關(guān)接口模式的開發(fā)實(shí)踐并不是很多,但是就算實(shí)際小型項(xiàng)目當(dāng)中用的不多,接口模式依然還是許多其他模式的基礎(chǔ)依賴,地位重要.
最后分享一個(gè)蠻有用的工具,上git的好東西.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/79681.html
摘要:為了方便廣大的開發(fā)者,特此統(tǒng)計(jì)了網(wǎng)上諸多的免費(fèi),為您收集免費(fèi)的接口服務(wù),做一個(gè)的搬運(yùn)工,以后會(huì)每月定時(shí)更新新的接口。將長(zhǎng)段中文切詞分開。 為了方便廣大的開發(fā)者,特此統(tǒng)計(jì)了網(wǎng)上諸多的免費(fèi)API,為您收集免費(fèi)的接口服務(wù),做一個(gè)api的搬運(yùn)工,以后會(huì)每月定時(shí)更新新的接口。有些接口來(lái)自第三方,在第三方注冊(cè)就可以成為他們的會(huì)員,免費(fèi)使用他們的部分接口。 百度AccessToken:針對(duì)HTTP ...
摘要:接口測(cè)試形式單個(gè)接口測(cè)試包含性能測(cè)試和通過接口調(diào)用進(jìn)行場(chǎng)景測(cè)試。充分來(lái)說(shuō)就是接口測(cè)試相對(duì)容易實(shí)現(xiàn)自動(dòng)化持續(xù)集成。 本文你將了解到 1、接口測(cè)試基本概念,包含什么是接口,什么是接口測(cè)試,為什么要做接口測(cè)試2、接口測(cè)試用例設(shè)計(jì)3、怎樣不用寫代碼,也能快速的根據(jù)開發(fā)的API文檔完成接口自動(dòng)化測(cè)試腳本 注:如果你對(duì)接口基本概念和接口測(cè)試用例已熟悉,可以直接跳過,其實(shí)看一遍也無(wú)防,就當(dāng)作 溫故知...
摘要:接口的對(duì)象可以利用子類對(duì)象的向上轉(zhuǎn)型進(jìn)行實(shí)例化賦值。接口文件保存在結(jié)尾的文件中,文件名使用接口名。接口相應(yīng)的字節(jié)碼文件必須在與包名稱相匹配的目錄結(jié)構(gòu)中。接口不能包含成員變量,除了全局常量定義。 概念 接口,在JAVA編程語(yǔ)言中是一個(gè)引用類型,是抽象方法的集合,接口通常以interface來(lái)聲明。一個(gè)類通過繼承接口的方式,從而來(lái)繼承接口的抽象方法。 接口中只能包含抽象方法和全局常量。 接...
摘要:接口和內(nèi)部類為我們提供了一種將接口與實(shí)現(xiàn)分離的更加結(jié)構(gòu)化的方法。 接口和內(nèi)部類為我們提供了一種將接口與實(shí)現(xiàn)分離的更加結(jié)構(gòu)化的方法。 1.抽象類和抽象方法 抽象類,是普通的類與接口之間的一種中庸之道. 抽象方法:僅有聲明而沒有方法體. 抽象類:包含抽象方法的類.如果一個(gè)類包含一個(gè)或多個(gè)抽象方法,該類必須被限定為抽象的. 如果從一個(gè)抽象類繼承,并想創(chuàng)建該新類的對(duì)象,那么久必須為基類中的所...
摘要:子類繼承抽象類,并具體實(shí)現(xiàn)方法。抽象類的使用區(qū)別于具體類,抽象類無(wú)法直接創(chuàng)建抽象類對(duì)象,但是可以聲明抽象類的變量,引用抽象類對(duì)應(yīng)具體子類對(duì)象。接口優(yōu)于抽象類中討論到一條規(guī)則接口優(yōu)于抽象類。接口聲明能力,抽象類提供默認(rèn)實(shí)現(xiàn)全部或部分方法。 接口 類,強(qiáng)調(diào)數(shù)據(jù)類型(自定義)的概念,在一些情況下,并不能反映對(duì)象以及對(duì)象操作的本質(zhì)。有時(shí)我們關(guān)注的并非對(duì)象的類型,而是對(duì)象的能力。 接口聲明一組功...
摘要:面向?qū)ο蠡驹瓌t單一職責(zé)原則與接口隔離原則面向?qū)ο蠡驹瓌t單一職責(zé)原則與接口隔離原則面向?qū)ο蠡驹瓌t里式代換原則與依賴倒置原則面向?qū)ο蠡驹瓌t最少知道原則與開閉原則一單一職責(zé)原則單一職責(zé)原則簡(jiǎn)介單一職責(zé)原則的英文名稱是,簡(jiǎn)稱。 面向?qū)ο蠡驹瓌t(1)- 單一職責(zé)原則與接口隔離原則 面向?qū)ο蠡驹瓌t(1)- 單一職責(zé)原則與接口隔離原則面向?qū)ο蠡驹瓌t(2)- 里式代換原則與依賴倒置原則面...
閱讀 3209·2021-11-10 11:36
閱讀 3155·2021-11-02 14:39
閱讀 1737·2021-09-26 10:11
閱讀 4975·2021-09-22 15:57
閱讀 1697·2021-09-09 11:36
閱讀 2057·2019-08-30 12:56
閱讀 3497·2019-08-30 11:17
閱讀 1707·2019-08-29 17:17