摘要:想繼續(xù)了解設(shè)計(jì)模式必須要先搞懂面向?qū)ο缶幊蹋駝t只會(huì)讓你自己更痛苦。創(chuàng)建型設(shè)計(jì)模式主要有簡(jiǎn)單工廠模式,工廠方法模式,抽象工廠模式,建造者模式,原型模式和單例模式,下面一一道來。而工廠方法模式本意是將實(shí)際創(chuàng)建對(duì)象的工作推遲到子類中。
接觸前端兩三個(gè)月的時(shí)候,那時(shí)候只是聽說設(shè)計(jì)模式很重要,然后我就去讀了一本設(shè)計(jì)模式的書,讀了一部分,也不知道這些設(shè)計(jì)模式到底設(shè)計(jì)出來干嘛的,然后就沒再看了。后來就自己做一些小項(xiàng)目也覺著好像不需要用到設(shè)計(jì)模式這個(gè)東西呀。現(xiàn)在,接觸前端有半年了,決定再重新看看設(shè)計(jì)模式,說不定會(huì)有一些啟發(fā)。于是發(fā)現(xiàn)了一本好書——《JavaScript設(shè)計(jì)模式》,寫的通俗易懂,用一個(gè)個(gè)故事串起了一整本書,看了一部分發(fā)現(xiàn)原來我平時(shí)寫代碼的時(shí)候無意之中就用到了一些設(shè)計(jì)模式,然后就忍不住都看完了。看完整本書,讓我完全改變了以前對(duì)設(shè)計(jì)模式的看法,也學(xué)到了很多在實(shí)際項(xiàng)目開發(fā)中的經(jīng)驗(yàn)。這里就簡(jiǎn)單總結(jié)下這本書,也算是做個(gè)筆記,供自己以后參考。(定義一般都比較晦澀難懂,可以先看看使用場(chǎng)景再回來理解相關(guān)定義)
先給個(gè)書的鏈接: JavaScript設(shè)計(jì)模式-張容銘
設(shè)計(jì)模式是代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié),為了可重用代碼,保證代碼的可靠性等。設(shè)計(jì)模式主要分為三大類型,創(chuàng)建型模式,結(jié)構(gòu)型模式和行為型模式,本書還額外寫了另兩類設(shè)計(jì)模式,技巧型模式和架構(gòu)型模式。JavaScript設(shè)計(jì)模式是以面向?qū)ο缶幊虨榛A(chǔ)的,JavaScript的面向?qū)ο缶幊毯蛡鹘y(tǒng)的C++、Java的面向?qū)ο缶幊逃行┎顒e,這讓我一開始接觸JavaScript的時(shí)候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續(xù)了解JavaScript設(shè)計(jì)模式必須要先搞懂JavaScript面向?qū)ο缶幊蹋駝t只會(huì)讓你自己更痛苦。
創(chuàng)建型設(shè)計(jì)模式創(chuàng)建型設(shè)計(jì)模式是一類處理對(duì)象創(chuàng)建的設(shè)計(jì)模式,通過某種方式控制對(duì)象的創(chuàng)建來避免基本對(duì)象創(chuàng)建時(shí)可能導(dǎo)致設(shè)計(jì)上的問題或增加設(shè)計(jì)上的復(fù)雜度。創(chuàng)建型設(shè)計(jì)模式主要有簡(jiǎn)單工廠模式,工廠方法模式,抽象工廠模式,建造者模式,原型模式和單例模式,下面一一道來。
簡(jiǎn)單工廠模式作者把簡(jiǎn)單工廠模式比喻成一個(gè)神奇的魔術(shù)師。
定義又叫靜態(tài)工廠方法,由一個(gè)工廠對(duì)象決定創(chuàng)建某一種產(chǎn)品對(duì)象類的實(shí)例,主要用來創(chuàng)建同一類對(duì)象。
使用場(chǎng)景看完上面的定義一定很不解,說的到底是啥,現(xiàn)在就舉個(gè)例子來解釋一下。比如體育商品店賣體育器材,里面有很多體育用品及其相關(guān)介紹。當(dāng)你來到體育用品店買一個(gè)籃球,只需問售貨員,他就會(huì)幫你找到你所要的東西。用程序?qū)崿F(xiàn)如下:
// 籃球基類 var Basketball = function() { this.intro = "籃球盛行于美國(guó)"; }; Basketball.prototype = { getMember: function() { console.log("每個(gè)隊(duì)伍需要5名隊(duì)員"); }, getBallSize: function() { console.log("籃球很大"); } }; // 足球基類 var Football = function() { this.intro = "足球盛行于美國(guó)"; }; Football.prototype = { getMember: function() { console.log("每個(gè)隊(duì)伍需要11名隊(duì)員"); }, getBallSize: function() { console.log("籃球很大"); } }; // 運(yùn)動(dòng)工廠 var SportsFactory = function(name) { switch(name) { case "NBA": return new Basketball(); case "wordCup": return new Football(); } };
當(dāng)你使用這個(gè)運(yùn)動(dòng)工廠時(shí)只需要記住SportsFactory這個(gè)工廠對(duì)象就好了,它會(huì)幫你找到你想要的。
簡(jiǎn)單工廠模式的理念是創(chuàng)建對(duì)象,上面例子是將不同的類實(shí)例化,但是簡(jiǎn)單工廠模式還可以創(chuàng)建相似對(duì)象,將相似的東西提取,不相似的針對(duì)性處理即可。這樣只需創(chuàng)建一個(gè)對(duì)象就可以替代多個(gè)類了。
團(tuán)隊(duì)開發(fā)不同于個(gè)人,對(duì)全局變量的限制很大,要盡量少得創(chuàng)建全局變量。如果有同一類對(duì)象在不同需求中重復(fù)使用,那么大部分是不需要重復(fù)創(chuàng)建的,要學(xué)會(huì)代碼復(fù)用。用簡(jiǎn)單工廠來創(chuàng)建對(duì)象,可以減少全局變量創(chuàng)建提高代碼復(fù)用率,它的使用場(chǎng)合限制在創(chuàng)建單一對(duì)象。
工廠方法模式作者把工廠方法模式比喻成一張名片。
定義通過對(duì)產(chǎn)品類的抽象使其創(chuàng)建業(yè)務(wù)主要負(fù)責(zé)用于創(chuàng)建多類產(chǎn)品的實(shí)例。
使用場(chǎng)景在實(shí)際開發(fā)中,需求的變更是很正常的,開始需求簡(jiǎn)單可以直接創(chuàng)建對(duì)象,類似的需求多了可以用簡(jiǎn)單工廠方法重構(gòu),但是如果需求不停變化,那么不僅要修改工廠函數(shù)還要添加類,這樣就沒完了。而工廠方法模式本意是將實(shí)際創(chuàng)建對(duì)象的工作推遲到子類中。
// 工廠類 var Factory = function(type, content) { if(this instanceof Factory) { var s = new this[type](content); return s; } else { // 防止使用者不知道這是一個(gè)類,忘了加new操作符創(chuàng)建,導(dǎo)致全局變量污染 return new Factory(type, content); } }; Factory.prototype = { Java: function(content) { // ... }, JavaScript: function(content) { // ... }, php: function(content) { // ... } };
這樣以后如果想添加其他類,只需要在Factory的原型里添加就可以了。
收獲與總結(jié)對(duì)于創(chuàng)建很多類的對(duì)象,簡(jiǎn)單工廠模式就不適合了,通過工廠模式可以輕松創(chuàng)建多個(gè)類的實(shí)例對(duì)象,而且避免了使用者與對(duì)象類之間的耦合,用戶不必關(guān)心創(chuàng)建該對(duì)象的具體類,只需調(diào)用工廠方法即可。
抽象工廠模式抽象工廠模式讓你感覺出現(xiàn)的都是幻覺。
定義通過對(duì)類的工廠抽象使其業(yè)務(wù)用于對(duì)產(chǎn)品類簇的創(chuàng)建,而不負(fù)責(zé)某一類產(chǎn)品的實(shí)例。
抽象類抽象類是一種聲明但不能使用的類,當(dāng)你使用的時(shí)候就會(huì)報(bào)錯(cuò)。JavaScript中的抽象類不能像傳統(tǒng)面向?qū)ο笳Z言那樣輕松地創(chuàng)建,我們可以在類的方法中手動(dòng)拋出錯(cuò)誤來模擬抽象類。你可能會(huì)想,這樣的類什么都不能做能有什么用?其實(shí)它在繼承上是很有用的。
使用場(chǎng)景抽象工廠模式不能用來創(chuàng)建具體對(duì)象,一般用它作為父類類創(chuàng)建一些子類。
// 抽象工廠方法 var VehicleFactory = function(subType, superType) { // 判斷抽象工廠中是否有該抽象類 if(typeof VehicleFactory[superType] === "function") { // 緩存類 function F() {}; // 繼承父類屬性和方法 F.prototype = new VehicleFactory[superType](); // 將子類構(gòu)造函數(shù)指向子類 subType.constructor = subType; // 子類原型繼承父類 subType.prototype = new F(); } else { // 不存在該抽象類拋出錯(cuò)誤 throw new Error("未創(chuàng)建該抽象類"); } }; // 小汽車抽象類 VehicleFactory.Car = function() { this.type = "car"; }; VehicleFactory.Car.prototype = { getPrice: function() { return new Error("抽象方法不能調(diào)用") } }; // 公交車抽象類 VehicleFactory.Bus = function() { this.type = "bus"; }; VehicleFactory.Bus.prototype = { getPrice: function() { return new Error("抽象方法不能調(diào)用"); } };
抽象工廠實(shí)際上是一個(gè)子類繼承父類的方法,在該方法中需要通過傳遞子類以及繼承父類的名稱。
收獲與總結(jié)抽象工廠模式是設(shè)計(jì)模式中最抽象的一種,也是創(chuàng)建模式中唯一一種抽象化創(chuàng)建模式。該模式創(chuàng)建出的結(jié)果不是一個(gè)真實(shí)的對(duì)象實(shí)例,而是一個(gè)類簇,指定了類的結(jié)構(gòu)。
建造者模式建造者模式告訴我們分即是合。
定義將一個(gè)復(fù)雜對(duì)象的構(gòu)建層與其表示層相互分離,同樣的構(gòu)建過程可采用不同的表示。
應(yīng)用場(chǎng)景現(xiàn)在有一個(gè)發(fā)布簡(jiǎn)歷的需求,就是幫別人在公司網(wǎng)站上發(fā)布簡(jiǎn)歷,但是這些簡(jiǎn)歷有一個(gè)需求,除了將興趣愛好以及一些特長(zhǎng)發(fā)布在頁面里,其他信息如聯(lián)系方式等不要發(fā)布在網(wǎng)站上,而且每個(gè)人想找的工作是可以分類的。這樣一些需求我們需要?jiǎng)?chuàng)建的東西就多了,這時(shí)候前面的三種工廠模式都不適合了,這里就可以用建造者模式。
建造者模式和只關(guān)心創(chuàng)建結(jié)果的工廠模式不同,雖然其目的也是創(chuàng)建一個(gè)對(duì)象,但是更多關(guān)心的是創(chuàng)建這個(gè)對(duì)象的整個(gè)過程。在本例中,我們需要的不僅僅是應(yīng)聘者的實(shí)例還要在創(chuàng)建過程中注意這位應(yīng)聘者有哪些興趣愛好等。
// 創(chuàng)建一位人類 var Human = function(param) { // 技能 this.skill = param && param.skill || "保密"; // 興趣愛好 this.hobby = param && param.hobby || "保密"; }; // 類人原型方法 Human.prototype = { getSkill: function() { return this.skill; }, getHobby: function() { return this.hobby; } }; // 實(shí)例化姓名類 var Named = function(name) { var that = this; // 構(gòu)造器,解析姓名的姓與名 (function(name, that) { that.wholeName = name; if(name.indexOf(" ") > -1) { that.FirstName = name.slice(0, name.indexOf(" ")); that.FirstName = name.slice(name.indexOf(" ")); } })(name, that); }; // 實(shí)例化職位類 var Work = function(work) { var that = this; // 構(gòu)造器,通過傳入的職位特征來設(shè)置相應(yīng)職位及描述 (function(work, that) { switch(work) { case "code": that.work = "工程師"; break; case "UI": case "UE": that.work = "設(shè)計(jì)師"; break; case "teach": that.work = "教師"; break; default: that.work = work; } })(work, that); }; // 更換期望的職位 Work.prototype.changeWork = function(work) { this.work = work; };
下面來創(chuàng)建一位應(yīng)聘者
// 應(yīng)聘者創(chuàng)建類 var Person = function(name, work) { // 創(chuàng)建應(yīng)聘者緩存對(duì)象 var _person = new Human(); // 創(chuàng)建應(yīng)聘者姓名解析對(duì)象 _person.name = new Named(name); // 創(chuàng)建應(yīng)聘者期望職位 _person.work = new Work(work); // 返回創(chuàng)建的應(yīng)聘者對(duì)象 return _person; }收獲與總結(jié)
建造者模式和前面幾種創(chuàng)建型設(shè)計(jì)模式不同,它關(guān)心對(duì)象的整個(gè)創(chuàng)建過程,因此通常將創(chuàng)建對(duì)象的類模塊化,這樣使創(chuàng)建類的每一個(gè)模塊都可以得到靈活的運(yùn)用與高質(zhì)量的復(fù)用。這種方式對(duì)于整個(gè)對(duì)象類的拆分無形中增加了結(jié)構(gòu)的復(fù)雜性,因此如果對(duì)象粒度很小,或者模塊間的復(fù)用率很低,不建議使用建造者模式。
原型模式原型模式是JavaScript語言之魂。
定義用原型實(shí)例指向創(chuàng)建對(duì)象的類,使用于創(chuàng)建新的對(duì)象的類共享原型對(duì)象的屬性以及方法。
使用場(chǎng)景還是關(guān)于子類繼承父類的問題,為了提高性能,對(duì)于每次創(chuàng)建的一些簡(jiǎn)單的而又有差異化的屬性可以放在構(gòu)造函數(shù)中,將一些消耗資源比較大的方法放在基類的原型中,這樣就可以避免不必要的消耗,這就是原型模式的雛形。
原型模式更多的是用在對(duì)象的創(chuàng)建上,比如創(chuàng)建一個(gè)實(shí)例對(duì)象的構(gòu)造函數(shù)比較復(fù)雜或者耗時(shí)比較長(zhǎng),或者通過創(chuàng)建多個(gè)對(duì)象來實(shí)現(xiàn)。此時(shí)最好不要用new關(guān)鍵字去復(fù)制這些基類,可以通過對(duì)這些對(duì)象屬性或者方法進(jìn)行復(fù)制來實(shí)現(xiàn)創(chuàng)建。首先要有一個(gè)原型對(duì)象的復(fù)制方法。
// 原型對(duì)象復(fù)制方法 function prototypeExtend() { var F = function() {}, args = arguments, i = 0, len = args.length; for (; i < len; i++) { // 遍歷每個(gè)模板對(duì)象中的屬性 for(var j in args[i]) { F.prototype[j] = args[i][j]; } } // 返回緩存類實(shí)例 return new F(); }
企鵝游戲中創(chuàng)建一個(gè)企鵝對(duì)象,如果沒有企鵝基類,只提供了一些動(dòng)作模板對(duì)象,可以通過實(shí)現(xiàn)這些模板對(duì)象的繼承來創(chuàng)建一個(gè)企鵝實(shí)例對(duì)象。
var penguin = prototypeExtend({ speed: 20, swim: function() { console.log("游泳速度" + this.speed); }, run: function() { console.log("奔跑速度" + this.speed); } })
這樣通過prototypeExtend創(chuàng)建的就是一個(gè)對(duì)象,不用再用new去創(chuàng)建一個(gè)新的實(shí)例對(duì)象。
收獲與總結(jié)原型模式實(shí)際上也是一種繼承,可以讓多個(gè)對(duì)象分享同一個(gè)原型對(duì)象的屬性和方法,這種繼承的實(shí)現(xiàn)是不需要?jiǎng)?chuàng)建的,而是將原型對(duì)象分享給那些繼承的對(duì)象。原型對(duì)象更適合在創(chuàng)建復(fù)雜的對(duì)象時(shí),對(duì)于那些需求一直在變化而導(dǎo)致對(duì)象結(jié)構(gòu)不停地改變時(shí),將那些比較穩(wěn)定的屬性與方法共用而提取的繼承的實(shí)現(xiàn)。
單例模式哈哈,讓你感受下一個(gè)人的寂寞。
定義又被稱為單體模式,只允許實(shí)例化一次的對(duì)象類。有時(shí)也可以用一個(gè)對(duì)象來規(guī)劃一個(gè)命名空間,井井有條地管理對(duì)象上的屬性和方法。
使用場(chǎng)景單例模式應(yīng)該是JavaScript中最常見的一種設(shè)計(jì)模式了,經(jīng)常為我們提供一個(gè)命名空間,來防止不同的人命名變量的沖突。還可以用它來創(chuàng)建一個(gè)小型的代碼庫。
var A = { Util: { util_method1: function() {}, util_method2: function() {} }, Tool: { tool_method1: function() {}, tool_method2: function() {} }, Ajax: { ajax_method1: function() {}, ajax_method2: function() {} } ... }
如果想使用這個(gè)代碼庫,像下面這樣訪問即可:
A.Util.util_method1(); A.Tool.tool_method2();收獲與總結(jié)
單例模式有時(shí)也被稱為單體模式,它是只允許實(shí)例化一次的對(duì)象類,有時(shí)這么做也是為了節(jié)省系統(tǒng)資源。JavaScript中單例模式經(jīng)常作為命名空間對(duì)象來實(shí)現(xiàn),通過單例對(duì)象,我們可以將各個(gè)模塊的代碼井井有條地梳理在一起。
結(jié)構(gòu)型設(shè)計(jì)模式結(jié)構(gòu)型設(shè)計(jì)模式關(guān)注于如何將類或?qū)ο蠼M合成更大、更復(fù)雜的結(jié)構(gòu),以簡(jiǎn)化設(shè)計(jì)。主要有外觀模式,適配器模式,代理模式,裝飾者模式,橋接模式,組合模式和享元模式。
外觀模式作者把這種模式比喻成一種套餐服務(wù)。
定義為一組復(fù)雜的子系統(tǒng)接口提供一個(gè)更高級(jí)的統(tǒng)一接口,通過這個(gè)接口使得對(duì)子系統(tǒng)接口的訪問更加容易。在JavaScript中有時(shí)也會(huì)用于對(duì)底層結(jié)構(gòu)兼容性做統(tǒng)一封裝來簡(jiǎn)化用戶使用。
使用場(chǎng)景為頁面文檔document對(duì)象添加點(diǎn)擊事件時(shí),如果直接用onclick來綁定事件,那么如果團(tuán)隊(duì)中再有人要為document綁定click事件時(shí),就會(huì)把之前綁定的那個(gè)時(shí)間覆蓋,因?yàn)檫@是DOM0級(jí)事件。我們應(yīng)該用DOM2級(jí)事件處理程序提供的addEventListener來實(shí)現(xiàn),然而老版本IE是不支持這個(gè)方法的,必須用attachEvent,這樣如果我們寫一個(gè)能兼容所有瀏覽器的方式操作起來就會(huì)更方便,這時(shí)候就可以用到外觀模式。為功能統(tǒng)一但方法不統(tǒng)一的接口提供一個(gè)統(tǒng)一的接口。
// 外觀模式實(shí)現(xiàn) function addEvent(dom, type, fn) { // 對(duì)于支持DOM2級(jí)事件處理程序的瀏覽器 if(dom.addEventListener) { dom.addEventListener(type, fn, false); // 對(duì)于不支持addEventListener但支持attachEvent的瀏覽器 } else if(dom.attachEvent) { dom.attachEvent("on" + type, fn); } else { dom["on" + type] = fn; } }
解決瀏覽器兼容問題只是外觀模式應(yīng)用的一部分,很多代碼庫中都是通過外觀模式來封裝多個(gè)功能,簡(jiǎn)化底層造作方法的。
收獲與總結(jié)當(dāng)一個(gè)復(fù)雜的系統(tǒng)提供一系列復(fù)雜的接口方法時(shí),為系統(tǒng)的管理方便會(huì)造成接口方法的使用及其復(fù)雜。通過外觀模式,對(duì)接口進(jìn)行二次封裝可以隱藏其復(fù)雜性。
適配器模式聽到這個(gè)是的名字,有沒有想到水管彎彎的場(chǎng)景呢?
定義將一個(gè)類(對(duì)象)的接口(方法或者屬性)轉(zhuǎn)化成另外一個(gè)接口,以滿足用戶需求,使類(對(duì)象)之間接口的不兼容問題通過適配器得以解決。
使用場(chǎng)景公司有個(gè)活動(dòng)頁面正在使用公司內(nèi)部開發(fā)的A框架,可是很多新來的同事使用A框架開發(fā)新的功能需求時(shí)總是感覺很吃力,而且能用的方法有限,為了讓新同事盡快融入項(xiàng)目的開發(fā),可以引入jQuery框架,由于A框架和jQuery框架很像,這樣就可以寫一個(gè)適配器而不需要將之前的代碼全用jQuery寫一遍。
適配器模式不僅在編程中很常見,在生活中這種模式也很常見,比如三角插頭充電器對(duì)于兩項(xiàng)插頭是不能用的,此時(shí)就需要一個(gè)三項(xiàng)轉(zhuǎn)兩項(xiàng)插頭電源適配器,這就是一種適配器模式,其實(shí)它就是為了兩個(gè)代碼庫所寫的代碼兼容運(yùn)行而書寫的額外代碼。
JavaScript中適配器模式還能適配兩個(gè)代碼庫,適配參數(shù),適配數(shù)據(jù),適配服務(wù)端數(shù)據(jù)等。以參數(shù)適配為例。
function doSomeThing(name, title, age, color, size, prize){}
記住這些參數(shù)的順序是很困難的,所以我們經(jīng)常是以一個(gè)參數(shù)對(duì)象方式傳入的,如下所示:
/** * obj.name: name * obj.title: title * obj.age: age * obj.color: color * obj.size: size * obj.prize: prize ***/ function doSomeThing(obj){}
然而當(dāng)調(diào)用的時(shí)候也不能確定傳遞的參數(shù)是否完整,如有一些必須得參數(shù)沒有傳入,一些參數(shù)有默認(rèn)值等,這個(gè)時(shí)候就可以用適配器來適配傳入的參數(shù)對(duì)象。
function doSomeThing(obj) { var _adapter = { name: "雨夜清荷", title: "設(shè)計(jì)模式", age: 24, color: "pink", size: 100, prize: 50 }; for(var i in _adapter) { _adapter[i] = obj[i] || _adapter[i]; } }收獲與總結(jié)
JavaScript中的適配器更多應(yīng)用在對(duì)象之間,為了使對(duì)象可用,通常會(huì)將對(duì)象拆分并重新包裝,這樣就要了解適配器對(duì)象的內(nèi)部結(jié)構(gòu),這也是與外觀模式的區(qū)別所在。
代理模式有沒有想到牛郎織女鵲橋相會(huì)的場(chǎng)景?
定義由于一個(gè)對(duì)象不能直接引用另一個(gè)對(duì)象,所以需要通過代理對(duì)象在這兩個(gè)對(duì)象之間起到中介作用。
使用場(chǎng)景跨域問題應(yīng)該是使用代理模式解決的一個(gè)最典型的問題。由于用戶模塊上傳的照片量越來越大,導(dǎo)致服務(wù)器需要將上傳模塊重新部署到另外一個(gè)域中,這就導(dǎo)致了跨域問題。我們可以將相冊(cè)頁面和上傳模塊所在的服務(wù)器抽象成兩個(gè)對(duì)象,想讓跨域兩端的對(duì)象之間實(shí)現(xiàn)通信,就需要找個(gè)代理對(duì)象來實(shí)現(xiàn)他們之間的通信。
代理對(duì)象有很多種,簡(jiǎn)單一點(diǎn)的如img之類的標(biāo)簽通過src可以向其他域下的服務(wù)器發(fā)送請(qǐng)求。不過這類請(qǐng)求是get請(qǐng)求,是單向的,不會(huì)有響應(yīng)數(shù)據(jù)。另外一種代理對(duì)象的形式是通過script標(biāo)簽。而我們需要的代理對(duì)象,是對(duì)頁面與瀏覽器間通信的,JSONP就實(shí)現(xiàn)了一種代理模式。我們知道src屬性可以實(shí)現(xiàn)get請(qǐng)求,因此可以在src指向的url地址上添加一些字段信息,服務(wù)器獲取這些字段信息,相應(yīng)生成一分內(nèi)容。
// 前端瀏覽器頁面
// 另一個(gè)域下的服務(wù)器請(qǐng)求接口
這種方式可以想象成合理的一只小船,通過小船將你的請(qǐng)求發(fā)送給對(duì)岸,然后對(duì)岸的人們將數(shù)據(jù)放在小船里為你帶回來。
收獲與總結(jié)代理模式除了在跨域問題中有很多應(yīng)用外,有時(shí)對(duì)對(duì)象的實(shí)例化對(duì)資源的開銷很大,如頁面加載初期加載文件有很多,此時(shí)能夠延遲加載一些圖片對(duì)頁面首屏加載時(shí)間收益是很大的,通過代理可以先加載預(yù)覽圖片然后再加載開銷大的圖片。
由此可見,代理模式可以解決系統(tǒng)之間耦合度以及系統(tǒng)資源開銷大的問題,通過代理對(duì)象可以保護(hù)被代理對(duì)象,使被代理對(duì)象不受外界的影響。
顯然房子裝修就是一種典型的裝飾者模式。
定義在不改變?cè)瓕?duì)象的基礎(chǔ)上,通過對(duì)其進(jìn)行包裝擴(kuò)展(添加屬性或者方法)使原有對(duì)象可以滿足用戶的更復(fù)雜需求。
使用場(chǎng)景靜止是相對(duì)的,運(yùn)動(dòng)是絕對(duì)的,所以沒有一成不變的需求。在實(shí)際項(xiàng)目開發(fā)中需求總在不斷變化,當(dāng)原有的功能已經(jīng)不能滿足用戶的需求時(shí),我們要做的就是在這個(gè)基礎(chǔ)上添磚加瓦,設(shè)置新功能和屬性來滿足用戶提出的需求,這就是裝飾者模式要做的。
// 裝飾者 var decorator = function(input, fn) { // 獲取事件源 var input = document.getElementById(input); // 若事件源已經(jīng)綁定事件 if(typeof input.onclick === "function") { // 緩存事件源原有回調(diào)函數(shù) var oldClickFn = input.onclick; // 為事件源定義新的事件 input.onclick = function() { // 事件源原有回調(diào)函數(shù) oldClickFn(); // 執(zhí)行事件源新增回調(diào)函數(shù) fn(); } } else { input.onclick = fn; } }收獲與總結(jié)
除了裝飾者模式,適配器模式也可以對(duì)原有對(duì)象進(jìn)行擴(kuò)展,所不同的是適配器進(jìn)行擴(kuò)展很多時(shí)候是對(duì)對(duì)象內(nèi)部結(jié)構(gòu)的重組,因此了解其自身結(jié)構(gòu)是必須的。而裝飾者模式對(duì)對(duì)象的擴(kuò)展是一種良性擴(kuò)展,不用了解其具體實(shí)現(xiàn),只是在外部進(jìn)行了一次封裝擴(kuò)展。
橋接模式作者把這種模式比喻成城市間的公路。
定義在系統(tǒng)沿著多個(gè)維度變化的同時(shí),又不增加其復(fù)雜度并已達(dá)到解耦。
使用場(chǎng)景有時(shí)候,頁面中一些小小細(xì)節(jié)的改變常常因邏輯相似而導(dǎo)致大片臃腫的代碼,讓頁面苦澀不堪。現(xiàn)在項(xiàng)目有一個(gè)需求,是要把頁面上部的用戶信息添加一些鼠標(biāo)劃過的特效,但是用戶信息由很多小組件組成,對(duì)于用戶名,鼠標(biāo)劃過直接改變背景色,但是像用戶等級(jí)、用戶消息這類部件只能改變里面的數(shù)字內(nèi)容,處理邏輯不太一樣。這樣就需要寫不少代碼,但是又會(huì)感覺很冗余。這時(shí)候,我們首先要提取共同點(diǎn),對(duì)想的抽象邏輯做抽象提取處理。
對(duì)于用戶信息模塊的每一部分鼠標(biāo)滑過與鼠標(biāo)離開兩個(gè)事件的執(zhí)行函數(shù)有很大一部分是相似的,比如它們都處理每個(gè)部件中的某個(gè)元素,它們都是處理元素的字體顏色和背景顏色。可以創(chuàng)建下面這樣一個(gè)函數(shù),解除this耦合。
function changeColor(dom, color, bg) { // 設(shè)置元素的字體顏色 dom.style.color = color; // 設(shè)置元素的背景顏色 dom.style.background = bg; }
接下來就是對(duì)具體元素綁定時(shí)間了,但是僅僅知道元素事件綁定與抽象提取的設(shè)置樣式方法changeColor是不夠的,需要用一個(gè)方法將他們鏈接起來,這個(gè)方法就是橋接方法,這種模式就是橋接模式。就像你開著車去沈陽,那么你就需要找到一條連接北京與沈陽的公路,才能順利往返兩地。
對(duì)于事件的橋接方法,可以用一個(gè)匿名函數(shù)來代替。
var spans = document.getElementsByTagName("span"); spans[0].onmouseover = function() { changeColor(this, "red", "#ffffd"); }收獲與總結(jié)
橋接模式最主要的特點(diǎn)是將實(shí)現(xiàn)層(如元素綁定事件)與抽象層(如修飾頁面UI邏輯)解耦分離,使兩部分可以獨(dú)立變化,橋接模式主要是對(duì)結(jié)構(gòu)之間的解耦。
組合模式作者把組合模式比喻成超值午餐,感覺很形象。
定義又稱部分-整體模式,將對(duì)象組合成樹形結(jié)構(gòu)以表示“部分整體”的層級(jí)結(jié)構(gòu)。組合模式使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性。
使用場(chǎng)景為強(qiáng)化首頁用戶體驗(yàn),項(xiàng)目經(jīng)理準(zhǔn)備在用戶首頁添加一個(gè)新聞模塊,當(dāng)然新聞的內(nèi)容是根據(jù)用戶平時(shí)關(guān)注的內(nèi)容挖掘的,因此有的人可能會(huì)顯示文字新聞,有的人可能會(huì)是圖片新聞等等。
我們先來仔細(xì)分析下這個(gè)需求,需求中的這些新聞大致可以分為相互獨(dú)立的幾種類型,對(duì)某類新聞做修改時(shí)不會(huì)影響到其他類的新聞,這樣可以將每一類新聞抽象成面向?qū)ο缶幊讨械囊粋€(gè)類,然后在這些新聞?lì)愔刑暨x一些組合成需要的模塊,這時(shí)候就可以用組合模式了。
在頁面中,組合模式更常用在創(chuàng)建表單上,比如注冊(cè)頁面可能有不同的表單提交模塊。對(duì)于這些需求,我們只需要有一個(gè)基本的個(gè)體,然后通過一定的組合即可實(shí)現(xiàn)。
組合模式能夠給我們提供一個(gè)清晰的組成結(jié)構(gòu),組合對(duì)象類通過繼承同一個(gè)父類使其具有統(tǒng)一的方法,這樣也方便了統(tǒng)一管理與使用。
享元模式作者把享元模式比喻成城市公交車,可以仔細(xì)思考一番。
定義運(yùn)用共享技術(shù)有效地支持大量的細(xì)粒度的對(duì)象,避免對(duì)象間擁有相同內(nèi)容造成多余的開銷。
使用場(chǎng)景現(xiàn)在有新聞的內(nèi)容太多,我們有了一個(gè)分頁顯示所有新聞的需求。一個(gè)簡(jiǎn)單直觀的做法就是頁面加載后異步請(qǐng)求新聞數(shù)據(jù),然后創(chuàng)建所有條新聞插入頁面中,需要顯示哪一頁就顯示哪一頁。但是這樣做有一個(gè)很大的問題,這樣一下子創(chuàng)建幾百條新聞同時(shí)插入頁面會(huì)造成多頁的開銷嚴(yán)重影響網(wǎng)頁的性能。這里的所有新聞都有相似的結(jié)構(gòu),只是內(nèi)容不同罷了,對(duì)于這種相同結(jié)構(gòu)造成多余開銷的問題,可以用享元模式來解決。
享元模式 主要是對(duì)其數(shù)據(jù)、方法共享分離,將數(shù)據(jù)和方法分成內(nèi)部數(shù)據(jù)、內(nèi)部方法和外部數(shù)據(jù)、外部方法。內(nèi)部方法與內(nèi)部數(shù)據(jù)指的是相似或共有的數(shù)據(jù)和方法,所以將其提取出來減少開銷。上面例子中,所有新聞個(gè)體都有共同的結(jié)構(gòu),應(yīng)該作為內(nèi)部數(shù)據(jù),而下一頁按鈕綁定的事件則是外部方法。同時(shí)為了使用內(nèi)部數(shù)據(jù)還需要提供一個(gè)操作方法。
var Flyweight = function() { // 已創(chuàng)建的元素 var created = []; // 創(chuàng)建一個(gè)新聞包裝容器 function create() { var dom = document.createElement("div"); // 將容器插入新聞列表容器中 document.getElementById("container").appendChild(dom); // 緩存新創(chuàng)建的元素 created.push(dom); // 返回創(chuàng)建的新元素 return dom; } return { // 獲取創(chuàng)建新聞元素方法 getDiv: function() { // 如果已創(chuàng)建的元素小于當(dāng)前頁元素總個(gè)數(shù)(5個(gè)),則創(chuàng)建 if(created.length < 5) { return created(); } else { // 獲取第一個(gè)元素,并插入去后面 var div = created.shift(); created.push(div); return div; } } } }
上面創(chuàng)建一個(gè)享元類,由于每頁只能顯示5條新聞,所以創(chuàng)建5個(gè)元素,保存在享元類內(nèi)部,可以通過getDiv方法來獲取創(chuàng)建的元素。下面就要實(shí)現(xiàn)外部數(shù)據(jù)和外部方法,外部數(shù)據(jù)就是我們要顯示的所有新聞內(nèi)容,由于每個(gè)內(nèi)容都不一樣肯定不能共享。首先,我們要根據(jù)新聞內(nèi)容實(shí)例化頁面,然后,對(duì)下一頁綁定一個(gè)點(diǎn)擊事件,顯示下一頁。
var paper = 0, num = 5, len = article.length; // 添加五條新聞 for(var i = 0; i < 5; i++) { if(article[i]) // 通過享元類獲取創(chuàng)建的元素并寫入新聞內(nèi)容 Flyweight.getDiv().innerHTML = article[i]; }
// 下一頁按鈕綁定事件 document.getElementById("next_page").onclick = function() { // 如果新聞內(nèi)容不足5條則返回 if(article.length < 5) { return; } var n = ++paper * num % len, // 獲取當(dāng)前頁的第一條新聞索引 j = 0; // 插入5條新聞 for(; j < 5; j++) { // 如果存在n+j條則插入 if(article[n + j]) { Flyweight.getDiv().innerHTML = article[n + j]; // 否則插入起始位置第n+j-len條 } else if(article[n + j - len]) { Flyweight.getDiv().innerHTML = article[n + j - len]; } else { Flyweight.getDiv().innerHTML = ""; } } }
這樣用享元模式對(duì)頁面重構(gòu)之后每次操作只需要操作5個(gè)元素,這樣性能可以提高很多。
收獲與總結(jié)享元模式的應(yīng)用是為了提高程序的執(zhí)行效率與系統(tǒng)性能,因此在大型系統(tǒng)開發(fā)中應(yīng)用比較廣泛,可以避免程序中的數(shù)據(jù)重復(fù)。應(yīng)用時(shí)一定要找準(zhǔn)內(nèi)部狀態(tài)與外部狀態(tài),這樣才能更合理地提取分離。
行為型設(shè)計(jì)模式行為型設(shè)計(jì)模式用于不同對(duì)象之間職責(zé)劃分或算法抽象,行為型設(shè)計(jì)模式不僅僅涉及類和對(duì)象,還涉及類或?qū)ο笾g的交流模式并加以實(shí)現(xiàn)。行為型設(shè)計(jì)模式主要有模板方法模式,觀察者模式,狀態(tài)模式,策略模式,職責(zé)鏈模式,命令模式,訪問者模式,中介者模式,備忘錄模式,迭代器模式和解釋器模式,這么多的模式真得好好消化一陣子了。
模板方法模式作者把這種模式比喻成照貓畫虎。
定義父類中定義一組操作算法骨架,而將一些實(shí)現(xiàn)步驟延遲到子類,使得子類可以不改變父類算法結(jié)構(gòu)的同時(shí)可重新定義算法中某些實(shí)現(xiàn)步驟。
使用場(chǎng)景提示框歸一化,一個(gè)網(wǎng)站有很多頁面,如果每個(gè)頁面的彈出框樣式不太一致就會(huì)顯得不是很和諧,需要將他們的樣式統(tǒng)一。新手最直觀的想法就是去每個(gè)頁面一個(gè)個(gè)修改,當(dāng)然這樣的代價(jià)是很大的,我們需要寫一個(gè)彈出框插件,將這些彈出框封裝好,然后再各個(gè)頁面調(diào)用即可。這是在這個(gè)插件中就可以使用模板方法模式了,不需要重復(fù)寫多個(gè)樣式。
模板方法模式就是將多個(gè)模型抽象畫歸一,從中抽象出一個(gè)最基本的模板,這個(gè)模板可以作為實(shí)體也可以作為抽象對(duì)象,其他模塊只需要繼承這個(gè)模板方法,也可以擴(kuò)展某些方法。
打個(gè)比方,我們生活中用蛋糕做模具做蛋糕,做出的蛋糕是外形相同的,因?yàn)樗麄兌加猛粋€(gè)模具。然而商店里面賣的蛋糕是各式各樣的,這都是對(duì)蛋糕的二次加工。我們的需求中基本提示框就是我們抽象出來的模具,其他提示框比這個(gè)提示框要多一些功能,我們只需要對(duì)他們做一些二次加工就能滿足需求了。
模板方法不僅在歸一化組件時(shí)使用,有時(shí)候創(chuàng)建頁面時(shí)也是很常用的,比如創(chuàng)建三類導(dǎo)航,第一類是基礎(chǔ)的,第二類是多了消息提醒功能的,第三類多了后面顯示網(wǎng)址功能。這也可以用模板方法實(shí)現(xiàn),此時(shí)抽象出來的基類是最簡(jiǎn)單的基礎(chǔ)導(dǎo)航類。
// 格式化字符串方法 function formateString(str, data) { return str.replace(/{#(w+)#}/g, function(match, key) { return typeof data[key] === undefined ? "": data[key] }); } // 基礎(chǔ)導(dǎo)航 var Nav = function(data) { // 基礎(chǔ)導(dǎo)航樣式模板 this.item = "{#name#}"; // 創(chuàng)建字符串 this.html = ""; // 格式化數(shù)據(jù) for(var i = 0, len = data.length; i < len; i++) { this.html += formateString(this.item, data[i]); } // 返回字符串?dāng)?shù)據(jù) return this.html; }
對(duì)于消息提醒導(dǎo)航類,只需額外添加消息提醒組件模板,并與消息提醒組件模板對(duì)傳入的網(wǎng)址數(shù)據(jù)進(jìn)行裝飾,得到所需的字符串,在調(diào)用從基類繼承的方法處理這些字符串即可。
var NumNav = function(data) { // 消息提醒信息組件模板 var tpl = "{#num#}"; // 裝飾數(shù)據(jù) for(var i = data.length - 1; i >= 0; i--) { data[i].name += data[i].name + formateString(tpl, data[i]); } // 繼承基礎(chǔ)導(dǎo)航類 return Nav.call(this, data); }收獲與總結(jié)
模板方法的核心在于對(duì)方法的重用,將核心方法封裝在基類中,讓子類繼承基類的方法,實(shí)現(xiàn)基類方法的共享,達(dá)到方法共用。子類繼承的方法是可擴(kuò)展的,這就需要對(duì)基類繼承的方法進(jìn)行重寫。
觀察者模式作者把這種模式比喻成通信衛(wèi)星。
定義又被稱作發(fā)布-訂閱模式或消息機(jī)制,定義了一種依賴關(guān)系,解決了主體對(duì)象與觀察者之間功能的耦合。
使用場(chǎng)景在團(tuán)隊(duì)開發(fā)中,經(jīng)常是一個(gè)人負(fù)責(zé)一個(gè)模塊,那么每人負(fù)責(zé)的模塊之間要如何進(jìn)行溝通呢?比如你實(shí)現(xiàn)一些需求需要添加一些代碼,但是這個(gè)需求需要其他模塊配合,但是每個(gè)模塊都是不同人寫的,你不想因?yàn)樾绿砑拥拇a影響到他人實(shí)現(xiàn)的功能,這個(gè)時(shí)候就需要用到觀察者模式了。
觀察者模式就是為了解決主體對(duì)象與觀察者之間的耦合。打個(gè)比方,目前每個(gè)國(guó)家都在研發(fā)并發(fā)射衛(wèi)星,發(fā)射這些衛(wèi)星是為了監(jiān)控一些信息,那么它就可以被看做一個(gè)觀察者或者說是一個(gè)消息系統(tǒng),如果讓這顆衛(wèi)星為飛機(jī)導(dǎo)航,那么這架飛機(jī)就是一個(gè)被觀察者或者說是一個(gè)主體對(duì)象。那么如果地面上的中轉(zhuǎn)站或者其他飛機(jī)需要知道這架飛機(jī)的信息,于是每當(dāng)飛機(jī)到達(dá)一個(gè)地方時(shí)就會(huì)向衛(wèi)星發(fā)出位子信息,然后衛(wèi)星又將信息廣播到已經(jīng)訂閱這架飛機(jī)的中轉(zhuǎn)站,這樣就可以避免一些飛機(jī)事故發(fā)生。
這時(shí)候,觀察者至少需要有兩個(gè)方法,一個(gè)是接收某架飛機(jī)發(fā)來的消息,一個(gè)是向訂閱的中轉(zhuǎn)站發(fā)送響應(yīng)消息。但是,并不是每個(gè)中轉(zhuǎn)站都要時(shí)刻監(jiān)控飛機(jī)狀態(tài)的,所以還需要一個(gè)取消注冊(cè)的方法。當(dāng)然這些消息還需要保存,就需要一個(gè)保存消息的容器。這時(shí)候觀察者雛形就出來了,他有一個(gè)消息容器和三個(gè)方法,訂閱消息方法,取消訂閱消息方法,發(fā)送訂閱消息方法。
var Observer = (function() { // 防止消息隊(duì)列暴露而被篡改,故將消息容器作為靜態(tài)私有變量保存 var __messages = {}; return { // 注冊(cè)信息接口 regist: function() {}, // 發(fā)布信息接口 fire: function() {}, // 移除信息接口 remove: function() {} } })();
下面就是可以自己具體實(shí)現(xiàn)這些接口了。
收獲與總結(jié)觀察者模式最主要是解決類或?qū)ο笾g的耦合,解耦兩個(gè)互相依賴的對(duì)象,使其依賴于觀察者的消息機(jī)制。這樣對(duì)于任何一個(gè)訂閱者來說,其他訂閱者對(duì)象的改變不會(huì)影響到自身,其自身既可以是消息的發(fā)出者也可以是消息的執(zhí)行者,這都依賴于調(diào)用觀察者對(duì)象中的三種方法(訂閱,注銷,發(fā)布消息)中的哪一種。
狀態(tài)模式作者把這種模式比喻成超級(jí)瑪麗。
定義當(dāng)一個(gè)對(duì)象內(nèi)部狀態(tài)發(fā)生改變時(shí),會(huì)導(dǎo)致其行為的改變,這看起來像是改變了對(duì)像。
使用場(chǎng)景平時(shí)寫代碼的時(shí)候經(jīng)常會(huì)遇到要寫很多條件判斷語句的情況,那么怎么減少代碼中的條件判斷語句呢?對(duì)于這類分支條件內(nèi)部獨(dú)立結(jié)果的管理,可以使用狀態(tài)模式,每一種條件作為對(duì)象的一種狀態(tài),面對(duì)不同的判斷結(jié)果,其實(shí)就是選擇對(duì)象內(nèi)的一種狀態(tài)。
將不同的判斷結(jié)果封裝在狀態(tài)對(duì)象內(nèi),然后該狀態(tài)對(duì)象返回一個(gè)可被調(diào)用的接口方法,用于調(diào)用狀態(tài)對(duì)象內(nèi)部的某種方法。
// 投票結(jié)果狀態(tài)對(duì)象 var ResultState = function() { // 判斷結(jié)果保存在內(nèi)部狀態(tài)中 var States = { // 每種狀態(tài)作為一種獨(dú)立方法保存 state0: function() { console.log("這是第一種情況"): }, state1: function() { console.log("這是第二種情況"): }, state2: function() { console.log("這是第三種情況"): }, state3: function() { console.log("這是第四種情況"): } } // 獲取某種狀態(tài)并執(zhí)行對(duì)應(yīng)方法 function show(result) { States["state" + result] && States["state" + result](); } return { // 返回調(diào)用狀態(tài)方法接口 show: show } }();
想調(diào)用第三種結(jié)果就可以如下調(diào)用
ResultState.show(3);
對(duì)于狀態(tài)模式,主要目的就是將條件判斷的不同結(jié)果轉(zhuǎn)化為狀態(tài)對(duì)象的內(nèi)部狀態(tài),這個(gè)內(nèi)部狀態(tài)一般作為狀態(tài)對(duì)象的私有變量,然后提供一個(gè)能夠調(diào)用狀態(tài)對(duì)象內(nèi)部狀態(tài)的接口方法對(duì)象即可。
收獲與總結(jié)狀態(tài)模式既是解決程序中臃腫的分支判斷語句問題,將每一個(gè)分支轉(zhuǎn)化為一種狀態(tài)獨(dú)立出來,方便每種狀態(tài)的管理又不至于每次只需時(shí)遍歷所有分支。
策略模式作者把這種模式比喻成活諸葛。
定義將定義的一組算法封裝起來,使其相互之間可以替換。封裝的算法具有一定獨(dú)立性,不會(huì)隨客戶端變化而變化。
使用場(chǎng)景年底的時(shí)候,公司商品展銷頁都要開展大促銷活動(dòng)。在圣誕節(jié),一部分商品5折出售,一部分商品8折出售,一部分商品9折出售,到元旦搞個(gè)幸運(yùn)反饋活動(dòng),普通用戶滿100返30,高級(jí)VIP用戶滿100返50。這個(gè)時(shí)候上面的狀態(tài)模式就不適用了,因?yàn)槊恳惶烀恳粋€(gè)商品只有一種促銷情況,這個(gè)時(shí)候可以用策略模式。
結(jié)構(gòu)上看,它與狀態(tài)模式很像,也是在內(nèi)部封裝一個(gè)對(duì)象,然后通過返回的接口對(duì)象實(shí)現(xiàn)實(shí)現(xiàn)對(duì)內(nèi)部對(duì)象的調(diào)用,不同點(diǎn)是,策略模式不需要管理狀態(tài)、狀態(tài)間沒有依賴關(guān)系、策略之劍可以相互替換、在策略對(duì)象內(nèi)部保存的是相互獨(dú)立的一些算法。看看策略對(duì)象的實(shí)現(xiàn):
// 價(jià)格策略對(duì)象 var PriceStrategy = function() { // 內(nèi)部算法對(duì)象 var strategy = { // 100返30 return30: function(price) {}, // 100返50 return50: function(price) {}, // 9折 percent90: function(price) {}, // 8折 percent80: function(price) {}, // 5折 percent50: function(price) {}, } // 策略算法調(diào)用接口 return function(algorithm, price) { return strategy[algorithm] && strategy[algorithm](price); } }();收獲與總結(jié)
策略模式主要特色是創(chuàng)建一系列策略算法,每組算法處理業(yè)務(wù)都是相同的,只是處理的過程或者處理的結(jié)果不一樣,所以它們是可以相互替換的,這樣就解決了算法與使用者之間的耦合。
職責(zé)鏈模式作者把這種模式比喻成一個(gè)有序車站。
定義解決請(qǐng)求的發(fā)送者與請(qǐng)求的接受者之間的耦合,通過職責(zé)鏈上的多個(gè)對(duì)象對(duì)分解請(qǐng)求流程,實(shí)現(xiàn)請(qǐng)求在多個(gè)對(duì)象之間的傳遞,知道最后一個(gè)對(duì)象完成請(qǐng)求的處理。
使用場(chǎng)景項(xiàng)目經(jīng)理準(zhǔn)備改善頁面中的輸入驗(yàn)證與提示交互體驗(yàn)。如用戶在輸入框輸入信息后,在輸入框的下面提示出一些備選項(xiàng),當(dāng)用戶輸入完成后,則要對(duì)用戶輸入信息進(jìn)行驗(yàn)證等,頁面中很多模塊需要用戶提交信息,為增強(qiáng)用戶體驗(yàn),這些輸入框大部分需要具備以上兩種功能。現(xiàn)在需要完成這個(gè)需求,但是以后可能要對(duì)原有表單交互體驗(yàn)做一些修改,也就是這是一個(gè)半成品需求。這種情況下,我們需要將需求里面需要做的每一件事情獨(dú)立出來,這樣完整的需求就變成一個(gè)個(gè)相互獨(dú)立的模塊需求,這樣就不會(huì)因?yàn)橐院笮枨蟮母淖兌绊懳覀冺?xiàng)目的進(jìn)展,這樣還有利于以后的單元測(cè)試。這其實(shí)就是一種職責(zé)鏈模式。
對(duì)于上面的需求,對(duì)輸入框綁定事件是第一部分,第二部分是創(chuàng)建xhr進(jìn)行異步數(shù)據(jù)獲取,第三部分就是適配響應(yīng)數(shù)據(jù),將接收到的數(shù)據(jù)格式化成可處理的形式,最后一部分是向組件創(chuàng)建器傳入相應(yīng)數(shù)據(jù)生成組件。
職責(zé)鏈模式定義了請(qǐng)求的傳遞方向,通過多個(gè)對(duì)象對(duì)請(qǐng)求的傳遞,實(shí)現(xiàn)一個(gè)復(fù)雜的邏輯操作。因此職責(zé)鏈模式將負(fù)責(zé)的需求顆粒化逐一實(shí)現(xiàn)每個(gè)最小分內(nèi)的需求,并將請(qǐng)求順序地傳遞。對(duì)于職責(zé)鏈上的每一個(gè)對(duì)象來說,它可能是請(qǐng)求的發(fā)起者也可能是請(qǐng)求的接收者,通過這種方式不僅僅簡(jiǎn)化原對(duì)象的復(fù)雜度,而且解決原請(qǐng)求的發(fā)起者與原請(qǐng)求的接收者之間的耦合。
命令模式 定義將請(qǐng)求與實(shí)現(xiàn)解耦并封裝成獨(dú)立對(duì)象,從而使不同的請(qǐng)求對(duì)客戶端的實(shí)現(xiàn)參數(shù)化。
使用場(chǎng)景現(xiàn)在的需求是要做一個(gè)活動(dòng)頁面,平鋪式的結(jié)構(gòu),不過頁面的每個(gè)模塊都有些相似的地方,比如每個(gè)預(yù)覽產(chǎn)品圖片區(qū)域,都有一行標(biāo)題,然后標(biāo)題下面是產(chǎn)品圖片,只是圖片的數(shù)量與排列不同。我們需要一種自由創(chuàng)建視圖模塊的方法,有時(shí)候創(chuàng)建多張圖片有時(shí)候只創(chuàng)建一張圖片,這時(shí)候可以試試命令模式。
命令模式是將創(chuàng)建模塊的邏輯封裝在一個(gè)對(duì)象里,這個(gè)對(duì)象提供一個(gè)參數(shù)化的請(qǐng)求接口,通過調(diào)用這個(gè)接口并傳遞一些參數(shù)實(shí)現(xiàn)調(diào)用命令對(duì)象內(nèi)部中的一些方法。請(qǐng)求部分很簡(jiǎn)單,只需要按照給定參數(shù)格式書寫指令即可,所以實(shí)現(xiàn)部分的封裝才是重點(diǎn),因?yàn)樗獮檎?qǐng)求部分提供所需方法。
那么哪些對(duì)象需要被命令化呢?既然需要?jiǎng)討B(tài)展示不同模塊,所以創(chuàng)建元素這一需求就是變化的,因此創(chuàng)建元素方法、展示方法應(yīng)該被命令化。
// 模塊實(shí)現(xiàn)模塊 var viewCommand = (function() { var tpl = { // 展示圖片結(jié)構(gòu)模塊 product: [ "",.....,"" ].join(""), // 展示標(biāo)題結(jié)構(gòu)模塊 title: [ "",.....,"" ].join(""), }, // 格式化字符串緩存字符串 html = ""; // 格式化字符串 function formateString(str, obj) {} // 方法集合 var Action = { // 創(chuàng)建方法 create: function(data, view) { // 解析數(shù)據(jù) if(data.length) { // 遍歷 for(var i = 0, len = data.length; i < len; i++) { html += formateString(tpl[view], data[i]); } } else { html += formateString(tpl[view], data); } }, // 展示方法 display: function(container, data, vuew) { // 如果傳入數(shù)據(jù) if(data) { // 根據(jù)給的數(shù)據(jù)創(chuàng)建視圖 this.create(data, view); } // 展示模塊 document.getElementById(container).innerHTML = html; // 展示后清空緩存字符串 html = ""; } } // 命令接口 return function excute(msg) { // 解析命令,如果msg.param不是數(shù)組則將其轉(zhuǎn)化為數(shù)組 msg.param = Object.prototype.toString.call(msg.param) === "[object Array]" ? msg.param : [msg.param]; // Action內(nèi)部調(diào)用的方法引用this,此處保證作用域this執(zhí)行傳入Action Action[msg.command].apply(Action, msg.param) } })();
下面就可以測(cè)試這個(gè)命令對(duì)象了:
var productData = [ { src: "command/02.jpg", text: "綻放的桃花" }, { src: "command/03.jpg", text: "陽光下的溫馨" } ], // 模塊標(biāo)題數(shù)據(jù) titleData = { title: "夏日里的一片溫馨", tips: "暖暖的溫情帶給人們家的感覺" } // 調(diào)用命令對(duì)象 viewCommand({ command: "display", param: ["title", titleData, "title"] }); viewCommand({ command: "create", param: ["product", productData, "product"] });
有了命令模式,想創(chuàng)建任何頁面視圖都是一件很簡(jiǎn)單的事情。
收獲與總結(jié)命令模式是將執(zhí)行的命令封裝,解決命令發(fā)起者與命令執(zhí)行者之間的耦合,每一條命令實(shí)質(zhì)上是一個(gè)操作。命令的是使用者不必了解命令執(zhí)行者的命令接口是如何實(shí)現(xiàn)的,只需要知道如何調(diào)用。
訪問者模式作者把這種模式比喻成駐華大使。
定義針對(duì)于對(duì)象結(jié)構(gòu)中的元素,定義在不改變對(duì)象的前提下訪問結(jié)構(gòu)中元素的新方法。
使用場(chǎng)景用DOM2級(jí)事件為頁面中元素綁定事件時(shí),為css設(shè)置一些樣式如下:
var bindEvent = function(dom, type, fn) { if(dom.addEventListener) { dom.addEventListener(type, fn, false); } else if(dom.attachEvent) { dom.attachEvent("on" + type, fn); } else { dom["on" + type] = fn; } } var demo = document.getElementById("demo"); bindEvent(demo, "click", function() { this.style.background = "red"; });
這個(gè)在IE瀏覽器中會(huì)出問題,因?yàn)镮E的attachEvent事件中this指向的竟然是window而不是這個(gè)元素,所以如果想獲取事件對(duì)象必須用window.e來獲取。這個(gè)問題可以借用訪問者模式來解決。
訪問者模式的思想是我們?cè)诓桓淖儾僮鲗?duì)象的同時(shí),為它添加新的操作方法,來實(shí)現(xiàn)對(duì)操作對(duì)象的訪問。下面看看IE的實(shí)現(xiàn)方式:
function bindIEEvent(dom, type, fn, data) { var data = data || {}; dom.attachEvent("on" + type, function(e){ fn.call(dom, e, data); }); };
上面實(shí)現(xiàn)方法的核心就是調(diào)用call方法,call方法的作用就是更改函數(shù)執(zhí)行時(shí)的作用域,這正是訪問者模式的精髓。
收獲與總結(jié)訪問者模式解決數(shù)據(jù)與數(shù)據(jù)操作方法之間的耦合,將數(shù)據(jù)的操作方法獨(dú)立于數(shù)據(jù),使其可以自由化演變。訪問者更適合那些數(shù)據(jù)穩(wěn)定但是數(shù)據(jù)的操作方法易變的環(huán)境下。
中介者模式作者把這種模式比喻成媒婆,好吧,我笑了這里。
定義通過中介者對(duì)象封裝一系列對(duì)象之間的交互,是對(duì)象之間不再相互引用,降低他們之間的耦合。有時(shí)中介者對(duì)象也可以改變對(duì)象之間的交互。
使用場(chǎng)景項(xiàng)目經(jīng)理準(zhǔn)備在用戶首頁上的導(dǎo)航模塊添加一個(gè)設(shè)置層,讓用戶可以通過設(shè)置層來設(shè)置導(dǎo)航展開樣式。但是頁面中好多模塊都有導(dǎo)航,這要改起來工作量也很大,上面講的觀察者模式雖然能解決模塊之間的耦合,但是這里我們并沒有需要向設(shè)置層發(fā)送請(qǐng)求的需求,設(shè)置層只是單向控制導(dǎo)航模塊內(nèi)導(dǎo)航的樣式。這樣的單向通信就可以使用中介者模式。
觀察者模式和中介者模式都是通過消息收發(fā)機(jī)制實(shí)現(xiàn),不過在觀察者模式中,一個(gè)對(duì)象既可以是消息的發(fā)送者也可以是消息的接收者,而中介者模式中消息的發(fā)送方只有一個(gè)就是中介者對(duì)象,而且中介者對(duì)象不能訂閱消息,只有那些活躍對(duì)象(訂閱者)才能訂閱中介者消息。
如果用中介者模式來解決上面的問題,那么中介者對(duì)象就是設(shè)置層模塊對(duì)象,它負(fù)責(zé)向各個(gè)導(dǎo)航模塊對(duì)象發(fā)送用戶設(shè)置消息,而各個(gè)導(dǎo)航模塊則應(yīng)該作為消息的訂閱者存在,實(shí)現(xiàn)如下:
// 中介者對(duì)象 var Mediator = function() { // 消息對(duì)象 var _msg = {}; return { // 訂閱消息方法,type:消息名稱 action:消息回調(diào)函數(shù) register: function(type, action) { // 如果消息存在 if(_msg[type]) // 存入回調(diào)函數(shù) _msg[type].push(action); else { // 不存在則建立消息容器 _msg[type] = []; _msg[type].push(action); } }, // 發(fā)布消息方法 send: function(type) { // 如果該消息已經(jīng)被訂閱 if(_msg[type]) { // 遍歷已存儲(chǔ)的消息回調(diào)函數(shù) for(var i = 0, len = _msg[type].length; i < len; i++) { // 執(zhí)行回調(diào)函數(shù) _msg[type][i] && _msg[type][i](); } } } } }();
這樣就創(chuàng)建了一個(gè)中介者對(duì)象,下面就可以利用這個(gè)中介者對(duì)象完成我們的需求了。
收獲與總結(jié)同觀察者模式一樣,中介者模式的主要業(yè)務(wù)也是通過模塊間或者對(duì)象間的復(fù)雜通信,來解決模塊間或?qū)ο箝g的耦合。在中介者模式中,訂閱者是單向的,只能是訂閱者而不能是發(fā)布者。而消息統(tǒng)一由中介者對(duì)象發(fā)布。
備忘錄模式 定義在不破壞對(duì)象的封裝性的前提下,在對(duì)象之外捕獲并保存該對(duì)象內(nèi)部狀態(tài)以便日后對(duì)象使用或者對(duì)象恢復(fù)到以前的某個(gè)狀態(tài)。
使用場(chǎng)景在前面提到的新聞頁面中,有上一頁和下一頁的按鈕,頁面的內(nèi)容是用異步請(qǐng)求獲取的。如果點(diǎn)擊下一頁按鈕接著再點(diǎn)擊上一頁那么之前那一頁又要進(jìn)行一次異步請(qǐng)求,這是多余的操作。因?yàn)榈谝淮我呀?jīng)獲取了數(shù)據(jù),不需要再發(fā)送多余的請(qǐng)求。這個(gè)時(shí)候可以用備忘錄模式來緩存請(qǐng)求過的數(shù)據(jù)。也就是說每次發(fā)生請(qǐng)求的時(shí)候?qū)Ξ?dāng)前狀態(tài)做一次記錄,將請(qǐng)求到的數(shù)據(jù)以及對(duì)應(yīng)得頁碼緩存下來,如果之后返回到之前瀏覽過的頁面,直接在緩存中查詢即可,不用發(fā)生異步請(qǐng)求。先創(chuàng)建一個(gè)新聞緩存器:
// Page備忘錄類 var Page = function() { // 信息緩存對(duì)象 var cache = {}; return function(page, fn) { // 判斷該頁數(shù)據(jù)是否在緩存中 if(cache[page]) { // 顯示該頁內(nèi)容 showPage(page, cache[page]); // 執(zhí)行成功回調(diào)函數(shù) fn && fn(); } else { // 否則異步請(qǐng)求 $.post("./data/getNewsData.php", { page: page }, function(res) { // 成功返回 if(res.errNo == 0) { showPage(page, res.data); cache[page] = res.data; fn && fn(); } else { // 處理異常 } }) } } }
上面代碼可以看出Page緩存器內(nèi)部緩存了每次請(qǐng)求回來的新聞數(shù)據(jù),這樣以后如果用戶想回看某頁新聞數(shù)據(jù)就不需要發(fā)送不必要的請(qǐng)求了。
收獲與總結(jié)備忘錄模式最主要的任務(wù)是對(duì)現(xiàn)有的數(shù)據(jù)或狀態(tài)進(jìn)行緩存,為將類某個(gè)時(shí)刻使用或恢復(fù)做準(zhǔn)備。但是當(dāng)數(shù)據(jù)量過大時(shí),會(huì)嚴(yán)重占用系統(tǒng)提供的資源,此時(shí)對(duì)緩存器的優(yōu)化是很有必要的,復(fù)用率低的數(shù)據(jù)緩存下來是不值得的。
迭代器模式作者把這種模式比喻成一個(gè)點(diǎn)鈔機(jī)。
定義在不暴露對(duì)象內(nèi)部結(jié)構(gòu)的同時(shí),可以順序地訪問聚合對(duì)象內(nèi)部的元素。
使用場(chǎng)景迭代器模式主要是解決重復(fù)循環(huán)迭代的問題,之前接觸過面向?qū)ο笳Z言的應(yīng)該都對(duì)迭代器有所了解。迭代器就是用來順序地訪問一個(gè)聚合對(duì)象內(nèi)部元素的,它可以簡(jiǎn)化我們遍歷操作,就行銀行里的點(diǎn)鈔機(jī),有了它可以大幅度降低我們的點(diǎn)鈔成本。下面創(chuàng)建一個(gè)常用的迭代器對(duì)象:
var Iterator = function(items, container) { // 獲取父元素 var container = container && document.getElementById(container) || document, // 獲取元素 items = container.getElementsByTagName(items), // 獲取元素長(zhǎng)度 length = items.length, // 當(dāng)前索引值 index = 0; // 緩存原生數(shù)組splice方法 var splice = [].splice; return { // 獲取第一個(gè)元素 first: function() {}, // 獲取最后一個(gè)元素 second: function() {}, // 獲取前一個(gè)元素 pre: function() {}, // 獲取后一個(gè)元素 next: function() {}, // 獲取某一個(gè)元素 get: function(num) {}, // 對(duì)每一個(gè)元素執(zhí)行某一個(gè)方法 dealEach: function(fn) {}, // 對(duì)某一個(gè)元素執(zhí)行某一個(gè)方法 dealItem: function(num, fn) {}, // 排他方式處理某一個(gè)元素 exclusive: function() {} } }
下面具體實(shí)現(xiàn)迭代器里面的這些方法,然后就可以用這個(gè)迭代器對(duì)象啦。
收獲與總結(jié)通過迭代器我們可以順序地訪問一個(gè)聚合對(duì)象中的每一個(gè)元素。在開發(fā)中,迭代器極大簡(jiǎn)化了代碼中的循環(huán)語句,使代碼結(jié)構(gòu)清晰緊湊。用迭代器去處理一個(gè)對(duì)象時(shí),只需要提供處理的方法,而不必去關(guān)心對(duì)象的內(nèi)部結(jié)構(gòu),這也解決了對(duì)象的使用者與對(duì)象內(nèi)部結(jié)構(gòu)之間的耦合。
解釋器模式 定義對(duì)于一種語言,給出其文法表示,并定義一種解釋器,通過使用這種解釋器來解釋語言中定義的句子。
使用場(chǎng)景一個(gè)頁面中的某些功能好壞有時(shí)是靠一定的數(shù)據(jù)依據(jù)支撐的。項(xiàng)目經(jīng)理想看看用戶對(duì)最近新增的功能使用情況,前后端要給出統(tǒng)計(jì)數(shù)據(jù),然而前端交互統(tǒng)計(jì)項(xiàng)中要給出交互元素路徑。這件事情與冒泡事件類似,只不過在這個(gè)路徑中還要關(guān)心同一層級(jí)中當(dāng)前元素的兄弟元素。比如下面的結(jié)構(gòu):
要獲取button相對(duì)于class為wrap的div元素的Xpath路徑,那么可以表示為DIV>DIV2>SPAN。
上面對(duì)需求的描述是一種文法,描述的是一組規(guī)則,現(xiàn)在要做的事實(shí)現(xiàn)一個(gè)規(guī)則解釋器來解釋上面的規(guī)則。首先要分析給出的文法,查找他們的相似點(diǎn),然后該清楚我們要先實(shí)現(xiàn)什么再實(shí)現(xiàn)什么,基本上問題就能解決了。
一些描述性語句,幾次功能的提取抽象,形成了一套語法法則,這就是解釋器模式要處理的事情。是否能應(yīng)用解釋器模式的一條重要準(zhǔn)則是能否根據(jù)需求解析出一套完整的語法規(guī)則,不論該語法規(guī)則簡(jiǎn)單或是復(fù)雜都是必須的。
技巧型設(shè)計(jì)模式技巧型設(shè)計(jì)模式是通過一些特定技巧來解決組件的某些方面的問題,這類技巧一般通過實(shí)踐經(jīng)驗(yàn)總結(jié)得到。這本書中總結(jié)了8種技巧型設(shè)計(jì)模式,分別是鏈模式,委托模式,數(shù)據(jù)訪問對(duì)象模式,節(jié)流模式,簡(jiǎn)單模板模式,惰性模式,參與者模式和等待者模式。有興趣的同學(xué)可以去買書來看哦,這里就不一一解釋了。
架構(gòu)型設(shè)計(jì)模式架構(gòu)型設(shè)計(jì)模式是一類框架結(jié)構(gòu),通過提供一些子系統(tǒng),指定它們的職責(zé),并將它們條理清晰地組織在一起。現(xiàn)在流行的前端框架都用了這種類型的設(shè)計(jì)模式。本書總結(jié)了6種架構(gòu)型設(shè)計(jì)模式,分別是同步模塊模式,異步模塊模式,Widget模式,MVC模式,MVP模式和MVVM模式。
學(xué)習(xí)設(shè)計(jì)模式的學(xué)習(xí)對(duì)于我們來說任重而道遠(yuǎn),我們需要在實(shí)踐中不斷思考不斷總結(jié)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/86616.html
摘要:詞法熟悉語法的開發(fā)者,箭頭函數(shù)在涉及綁定時(shí)的行為和普通函數(shù)的行為完全不一致。被忽略的作為的綁定對(duì)象傳入,使用的是默認(rèn)綁定規(guī)則。使用內(nèi)置遍歷數(shù)組返回迭代器函數(shù)普通對(duì)象不含有,無法使用,可以進(jìn)行改造,個(gè)人博客地址 this詞法 熟悉ES6語法的開發(fā)者,箭頭函數(shù)在涉及this綁定時(shí)的行為和普通函數(shù)的行為完全不一致。跟普通this綁定規(guī)則不一樣,它使用了當(dāng)前的詞法作用域覆蓋了this本來的值。...
摘要:代碼之髓讀后感如何高效的學(xué)習(xí)語言技術(shù)讀后感王垠如何掌握程序語言代碼之髓這本書里提出了三種學(xué)習(xí)語言的方法如何高效的學(xué)習(xí)語言在比較中學(xué)習(xí)在歷史中學(xué)習(xí)在實(shí)踐中學(xué)習(xí)在比較中學(xué)習(xí)通過比較多種語言,總結(jié)出某種語言的獨(dú)有特點(diǎn),以及多種語言的共有特點(diǎn)。 title: 代碼之髓讀后感——如何高效的學(xué)習(xí)語言date: 2017-07-08 17:17:00categories: 技術(shù)tags: 讀后感 ...
摘要:遮蔽效應(yīng)作用域查找會(huì)在找到第一個(gè)匹配的標(biāo)識(shí)符時(shí)停止,不會(huì)繼續(xù)往上層作用域查找,這就會(huì)產(chǎn)生遮蔽效應(yīng)。會(huì)發(fā)現(xiàn)每一次輸出的都是為啥勒所有的回調(diào)函數(shù)回在循環(huán)結(jié)束后才會(huì)執(zhí)行事件循環(huán)。 三劍客 編譯,顧名思義,就是源代碼執(zhí)行前會(huì)經(jīng)歷的過程,分三個(gè)步驟, 分詞/詞法分析,將我們寫的代碼字符串分解成多個(gè)詞法單元 解析/語法分析,將詞法單元集合生成抽象語法樹(AST) 代碼生成,抽象語法樹(AST)轉(zhuǎn)...
摘要:今天,我無意中看到這樣一個(gè)東西,它叫做,這是一個(gè)開源免費(fèi)的適用于各種移動(dòng)端的觸摸滑動(dòng)插件。同時(shí)導(dǎo)航欄也是可以手動(dòng)滑動(dòng)的,當(dāng)用戶手動(dòng)滑動(dòng)導(dǎo)航欄,點(diǎn)擊某一個(gè)板塊時(shí),下面的內(nèi)容部分會(huì)隨即滑到相應(yīng)的內(nèi)容塊。 今天,我無意中看到這樣一個(gè)東西,它叫做Swiper,這是一個(gè)開源免費(fèi)的適用于各種移動(dòng)端的觸摸滑動(dòng)插件。看了一遍文檔,發(fā)現(xiàn)并不是很難,于是打算動(dòng)手自己寫一個(gè)Swiper官網(wǎng)上的稍復(fù)雜點(diǎn)的小d...
摘要:最近在讀語言精粹這本書,作者是是一名來自的資深架構(gòu)師,以創(chuàng)建和維護(hù)格式而為大家所熟知。三元運(yùn)算符有三個(gè)運(yùn)算數(shù)。嘗試從的成員屬性中取值將會(huì)導(dǎo)致異常。這個(gè)過程稱為委托。通過可取得它們所屬對(duì)象的上下文的方法稱為公共方法。 最近在讀《JavaScript語言精粹》這本書,作者是 Douglas Crockford;Douglas Crockford是一名來自 Yahoo!的資深JavaScri...
閱讀 769·2021-11-23 09:51
閱讀 835·2021-11-23 09:51
閱讀 2503·2021-11-15 18:01
閱讀 3862·2021-10-11 11:07
閱讀 2396·2021-09-22 15:30
閱讀 1075·2021-09-22 14:59
閱讀 1557·2019-08-30 15:55
閱讀 1753·2019-08-30 15:52