摘要:代理模式代理模式為一個對象提供一個代用品或占位符,以便控制對于它訪問。這種代理就叫虛擬代理。保護代理用于對象應(yīng)該有不同訪問權(quán)限情況。寫時復制代理時虛擬代理的一種變體。
一、創(chuàng)建型設(shè)計模式(三大類設(shè)計模式)
創(chuàng)建型設(shè)計模式 --"創(chuàng)建"說明該類別里面的設(shè)計模式就是用來創(chuàng)建對象的,也就是在不同的場景下我們應(yīng)該選用什么樣的方式來創(chuàng)建對象。
1. 單例模式==單例模式(Singleton)==:確保一個類只有一個實例,提供一個全局訪問點。
==標準==單例實現(xiàn)
通常要實現(xiàn)一個單例模式并不復雜,無非就是用一個變量來標記當前是否已經(jīng)為了某個類創(chuàng)建過對象了,如果創(chuàng)建過對象了,則在下一次獲取該類的實例時,直接返回之前創(chuàng)建的對象。
/*單例實現(xiàn)代碼如下:*/ var Singleton = function(name) { this.name = name; this.instance = null; }; Singleton.prototype.getName = function() { alert(this.name); }; Singleton.getInstance = function(name) { if (!this.instance) { this.instance = new Singleton(name); } return this.instance; }; //測試 var xiaoyi = Singleton.getInstance("小一"); var xiaoer = Singleton.getInstance("小二"); alert( xiaoyi === xiaoer ); //true
/*另一種寫法*/ var Singleton = function(name) { this.name = name; }; Singleton.prototype.getName = function() { alert ( this.name ); }; Singleton.getInstance = (function(){ var instance = null; return function(name) { if ( !instance ) instance = new Singleton(name); } return instance; })(); //測試 var xiaoyi = Singleton.getInstance("小一"); var xiaoer = Singleton.getInstance("小二"); alert( xiaoyi === xiaoer ); //true
結(jié)論: 無論我們創(chuàng)建的實例叫什么他總是同一個實例,這種寫法實現(xiàn)的單例雖然較為簡單,但是增加了類的“不透明性”,使用這必須知道這個是一個單例類。
透明單例
(試想一下,當我們單擊登錄按鈕的時候,頁面中會出現(xiàn)一個登錄浮窗,這個浮窗應(yīng)該是唯一的,無論單機多少次按鈕,每個浮窗只會被創(chuàng)建一次,那么這個浮窗就能用單例模式來創(chuàng)建)
現(xiàn)在我們來實現(xiàn)一個“透明”的單例類,這樣用戶使用這個類就能像使用其他類一樣。我們將創(chuàng)建CreateDiv單例類,他的作用是負責在頁面創(chuàng)建唯一的div節(jié)點。
/*使用透明的單例類來創(chuàng)建div*/ var CreateDiv = (function() { var instance; var CreateDiv = function( html ) {//傳入內(nèi)容 if(instance){ return instance; } this.html = html; //當前 this.init(); return instance = this; }; CreateDiv.prototype.init = function() { var div = document.createElement("div"); div.innnerHTML = this.html; document.body.appendChild(div); }; return CreateDiv; })(); //測試 var xiaoyi = new CreateDiv("小一"); var xiaoer = new CreateDiv("小二"); alert( xiaoyi === xiaoer ); //true
結(jié)論上邊這個單例雖然“透明”了,但是為了把instance封裝起來,我們使用了自執(zhí)行匿名函數(shù)和閉包,并且讓匿名函數(shù)返回真正的Singleton構(gòu)造方法。增加了程序復雜度。
CreateDiv的構(gòu)造函數(shù)實際上負責了兩件事情。第一:創(chuàng)建對象,執(zhí)行初始話方法,第二:保證只有一個實例對象。違背了單一原則。(單一原則:一個類最好只負責一項功能。控制類的粒度)
==導致的結(jié)果==當我們要使用這個類實例多個對象的時候,就必須要把控制創(chuàng)建唯一對象的那段去掉,這種修改會給我們帶來不必要的煩惱。
javascript中的單例
關(guān)于前邊所提到的幾種單例模式的實現(xiàn),更多是接近于傳統(tǒng)面向?qū)ο笳Z言中的實現(xiàn),單例從類創(chuàng)建而來,在以類為中心的語言中,這是一種很自然的做法。比如java中如果需要某個對象,就必須先定義一個類,對象總是從類中創(chuàng)建而來。
就javascript而言,它其實是一門無類(class-free)語言,在javascript中,既然我們只需要一個“唯一”的對象,為什么要先創(chuàng)建一個類?在javascript中經(jīng)常把全局變量當成單例來使用。
var a = {};
用這種形式創(chuàng)建對象a,a是獨一無二的。聲明于全局下,提供全局訪問點也是必然的。
這種做法缺陷:全局變量存在很多問題,容易造成命名空間污染。自己命名變量隨時可能被別人覆蓋掉了。【處理】作為開發(fā)這我們應(yīng)該盡量減少全局變量的使用,即使使用也要把它的污染降到最低。
幾種降低全局變量帶來的命名污染。
使用命名空間
var namespace = { a: {}, b: {}, };
使用閉包封裝私有變量
var user = (function(){ var _name = "sven", _age = 29; return { getUserInfo: function() { return _name + "-" _age; } } })()
通用的惰性單例
var getSingle = function(fn) { var result; return function() { return result || (result = fn.apply(this, arguments)); }; }; var createLoginLayer = function() { var div = document.createElement("div"); div.innerHTML = "我是登錄浮窗"; div.style.display = "none"; document.body.appendChild(div); return div; }; var CreateSingleLoginLayer = getSingle(createLoginLayer); document.getElementById("loginBtn").onclick = function() { var loginLayer = CreateSingleLoginLayer(); loginLayer.style.display = "block"; }; var CreteSingleIframe = getSingle(function() { //動態(tài)加載第三頁面 var iframe = document.createElement("iframe"); document.body.appendChild(iframe); return iframe; }); document.getElementById("loginBtn").onclick = function() { var loginLaryer = createSingleIframe(); loginLayer.src = "http://baidu.com" }
結(jié)論 以上我們把創(chuàng)建實例對象的職責和管理單例的職責分別放置在兩個方法里,這兩個方法可以獨立變化而互不影響,而當他們連接在一起,就完成了創(chuàng)建唯一實例對象的功能。
總結(jié)當需求實例唯一、命名空間時,就可以使用單例模式。結(jié)合閉包特性,用途廣泛。
二、結(jié)構(gòu)型設(shè)計模式結(jié)構(gòu)型設(shè)計模式 --關(guān)注于如何將類或者對象組合成更大的結(jié)構(gòu),以便在使用的時候更簡化。
1. 代理模式==代理模式(proxy)==:為一個對象提供一個代用品或占位符,以便控制對于它訪問。
代理模式在生活中的場景
比如:每個明星都有經(jīng)紀人作為代理。如果想請明星來辦一場商業(yè)演出,那么只能聯(lián)系它的經(jīng)紀人。經(jīng)紀人會把商業(yè)演出的細節(jié)和報酬都談好之后,再把合同交給明星簽名確認。
graph LR 客戶((圓))-->本體((圓)) graph LR 客戶((圓))-->代理((圓))-->本體((圓))
代理相當于本體的替身,替身對請求做出一些處理后再把請求給本體對象。
故事場景
康康喜歡上瑪麗亞,康康在二月十四想送一束花表白瑪麗亞,剛好她兩有共同的朋友michael,所以康康就叫micheal幫忙送花給瑪麗亞。
/*用代碼來寫送花場景,首先是不通過(代理)*/ var Flower = function() {}; var kangkang = { //類 sendFlower: function(target) { //傳入送花目標 var flower = new Flower(); //實例一朵花 target.receiveFlower(flower); //花傳給maria } }; var maria = { //類 receiveFlower: function(flower) { //接受類 console.log("收到花了" + flower) } }; kangkang.sendFlower(maria);
/*代理送花代碼*/ var Flower = function() {}; var kangkang = { sendFlower: function(target){ var flower = new Flower(); target.receiveFlower(flower); } }; var michael = {//michael為代理 receiverFlower: function(flower) { maria.receiveFlower(flower); } }; var maria = { receiveFlower: function(flower) { console.log("收到花了" + flower) } }; kangkang.sendFlower(michael);
顯然看起來這樣送花不是有病? 能直接送到手為什么非要轉(zhuǎn)別人的手送花呢。其實不然,當康康自己去送話,如果瑪麗亞心情號成功幾率有60%,但是心情差成功接近0,而通過jane的話jane可能較為了解瑪麗亞,選擇心情好的時候去送,這時候就事半功倍了。
/*心情好的時候送花*/ var Flower = function() {}; var kangkang = { sendFlower: function(target){ var flower = new Flower(); target.receiveFlower(flower); } }; var jane = {//jane為代理 receiverFlower: function(flower) { maria.listenGoodMood(function(){ //心情轉(zhuǎn)好是后送花 maria.receiveFlower(flower); }) } }; var maria = { receiveFlower: function(flower) { console.log("收到花了" + flower) }, listenGoodMood: function(fn) {//一秒后心情轉(zhuǎn)好 setTimeout(function() { if(true){fn()} fn(); }, 1000); } }; kangkang.sendFlower(jane);
保護代理和虛擬代理
從上面的例子我們可以看出這兩種代理的影子。代理jane可以幫助瑪麗亞過濾掉一些請求,比如說送花的人不是康康,這樣就直接可以在代理jane中被拒絕掉了。這種代理叫做保護代理。
假設(shè)new Flower一束花有期限,過期會凋謝,那么我們可以把new flower 交給代理jane去執(zhí)行,代理jane會選擇在瑪麗亞心情好的時候再去買花而后送花。這種代理就叫虛擬代理。(虛擬代理是把一些開銷很大的對象,延遲到真正需要它的時候再去創(chuàng)建。)
/*虛擬代理*/ var jane = {//jane為代理 receiverFlower: function(flower) { maria.listenGoodMood(function(){ //心情轉(zhuǎn)好是后送花 var flower = new Flower(); //延遲創(chuàng)建 flower 對象 maria.receiveFlower(flower); }) } };
虛擬代理實現(xiàn)圖片的預加載
如果直接給某個img設(shè)置src屬性的話,可能會由于網(wǎng)絡(luò)太差,圖片的位置往往有段時間會是一片空白,常見的作法就是用一張loading圖片占位,然后用異步的方式加載圖片,等圖片加載好了再把它填充把img節(jié)點中。這種場景就用虛擬代理。
/*創(chuàng)建一個圖片元素并且提供一個設(shè)置s"r"c的接口*/ var myImage = (function() { var imgNode = document.createElement("img"); document.body.appendChild(imgNode); return { setSrc: function(src) { imgNode.src = src; } } })(); /*引入代理ProxyImg對象,在圖片真正被加載好之前用一張展位的菊花圖來提示用戶正在加載。*/ var proxyImage = (function(){ var img = new Image; img.onload = function() { //當真正的圖加載完了在把圖片塞給他 myImage.setSrc(this.src) } return { setSrc: function(src) { //一開始先設(shè)置一張菊花圖給他 myImage.setSrc("./loading.gif"); img.src = src; } } })(); proxyImage.setSrc("http://wangshangtupian.jpg"); //模擬的圖片
代理的意義
單一職責原則就一個類而言,應(yīng)僅有一個引起它變化的原因。
開放-封閉閉原則不用改變MyImage類或者添加接口,通過代理給系統(tǒng)添加了新的行為,這符合開閉原則。
結(jié)論當我們不需要預加載的功能,我們只需要直接請求本體,而不需要請求代理,這就使用代理的靈活之處。
虛擬代理合并HTTP請求
先想象一下這樣一個場景:假設(shè)一個公司需要員工寫周報,周報要交給總監(jiān)批閱,總監(jiān)手下有150個員工,如果我們每個人把周報發(fā)給總監(jiān),那總監(jiān)可能就不用工作了,每周看周報。如果將周報發(fā)給組長,組長整理后再發(fā)給總監(jiān),那總監(jiān)就輕松多了。
/*需要做一個文件同步的功能,當點擊checkbox同時往另一臺服務(wù)器同步文件,如果一秒點四個checkbox,那么網(wǎng)絡(luò)開銷會相當大。我們可以通過一個代理函數(shù)來收集一段時間內(nèi)的請求,最后一次性發(fā)給服務(wù)器請求*/ 1 2 3 4 5 //給checkout 綁定事件 var synchronousFile = function(id) { console.log("開始同步文件, id為:" + id); }; var ProxySynchronousFile = (function(){ var cache = [],//保存一段事件內(nèi)需要同步的id timer; //定時器 return function (id){ cache.push(id); if(timer){ //保證不會覆蓋已經(jīng)啟動的定時器 return; } timer = setTimeout(function() { synchronousFile(cache.join(",")); //2秒后向服務(wù)器發(fā)送id的集合 clearTimeout(timer); timer = null; cache.length = 0; //清空ID集合 }, 2000); } })(); var checkbox = document.getElementsByTagName("input"); for (var i = 0, c; c= checkbox[i++];){ c.onclick = function() { if(this.checked === true){ ProxySynchronousFile(this.id); } } }
緩存代理
緩存代理可以為一些開銷大的運算結(jié)果提供暫時的存儲,在下次運算時,如果傳遞進來的參數(shù)跟之前一致,則可以直接傳出之前的存儲的運算結(jié)果。
/*計算乘積*/ var mult = function() { console.log("開始計算乘積"); var a = 1; for (var i = 0, l = arguments.length; i < l; i++){ a = a * arguments[i]; } return a; } var proxyMult = (function(){ var cache = {}; return function() { var args = Array.prototype.join.call(arguments, ","); if(args in cache){ return cache[args]; } return cache[args] = mult.apply(this, arguments); } })(); proxyMult(1, 2, 3, 4); //24 proxyMult(1, 2, 3, 4); //24
總結(jié)代理模式應(yīng)用廣泛,比如說防火墻代理:控制網(wǎng)絡(luò)資源的訪問,保護主機不讓壞人破壞。遠程代理:為一個對象在不同的地址空間提供局部代表。保護代理:用于對象應(yīng)該有不同訪問權(quán)限情況。智能引用代理:取代了簡單的指針,他在訪問對象是執(zhí)行一些附加操作,比如計算一個對象被應(yīng)用的次數(shù)。寫時復制代理:通常用于復制一個龐大的對象的情況。寫時復制代理延遲了復制的過程。當對象真正被修改時,才對它進行復制操作。寫時復制代理時虛擬代理的一種變體。更多就不再一一贅述了。
三、行為型設(shè)計模式行為型設(shè)計模式 --不單單涉及到類和對象,更關(guān)注于類或者對象之間的通訊交流。
1. 狀態(tài)模式==狀態(tài)模式(State Pattern)==:關(guān)鍵是區(qū)分事物內(nèi)部的狀態(tài),事物內(nèi)部的狀態(tài)改變往往會帶來事物行為的改變。
初識狀態(tài)模式
想象一下:有一個電燈,只有一個控制電燈的開關(guān)。當電燈開著的時候按下開關(guān),電燈會切換到關(guān)閉狀態(tài);再按一次開關(guān),電燈將又被打開,同一個開關(guān)在不同的狀態(tài)下表現(xiàn)出來的行為是不一樣的。
var Light = function() { this.state = "off"; //電燈初始狀態(tài) this.button = null; //電燈開關(guān) } Light.prototype.init = function() { var button = document.createElement("button"). self = this; button.innerHTML = "開關(guān)"; this.button = document.body.appendChild(button); this.button.onclick = function() { self.buttonWasPressed();//開關(guān)按下的行為函數(shù) } }; Ligth.prototype.buttonWasPressed = function() { if (this.state === "off") { console.log("開燈"); this.state = "on"; } else if (this.state === "on") { console.log("關(guān)燈"); this.state = "off"; } }; var light = new Light(); light.init();
令人遺憾的是這個世界上的電燈并不是只有這一種。某個酒店的燈,按一下開關(guān)弱光,在按一下開關(guān)強光,再按一下開光關(guān)閉。
Ligth.prototype.buttonWasPressed = function() { if (this.state === "off") { console.log("弱光"); this.state = "ruoguang"; } else if (this.state === "ruoguang") { console.log("強光"); this.state = "qiangguang"; } else if (this.state === "qiangguang"){ console.log("關(guān)燈"); this.state = "off"; } };
這個buttonWasPressed很明顯違反了開放-閉合原則,每次新增或修改電燈的狀態(tài)都需要從新改動buttonWasPressed方法中的代碼,這讓它成為一個非常不穩(wěn)定的方法。
/*讓我們改進一下*/ var OffLightState = function(light){ this.light = light; } OffLightState.prototype.buttonWasPressed = function() { console.log("弱光"); this.light.setState(this.light.weakLightState); } var WeakLightState = function(light){ this.light = light; } WeakLightState.prototype.buttonWasPressed = function() { console.log("強光"); this.light.setState(this.light.strongLightState); } var strongLightState = function(light){ this.light = light; } strongLightState.prototype.buttonWasPressed = function() { console.log("關(guān)燈"); this.light.setState(this.light.OffLightState); } //改寫類 var Light = function() { this.OffLightState = new OffLightState(this); this.WeakLightState = new WeakLightState(this); this.strongLightState = new strongLightState(this); this.button = null; }; Light.prototype.init = function(){ var button = document.createElement("button"), self = this; this.button = document.body.appendChild(button); this.button.innerHTML = "開關(guān)"; this.currState = this.offLightState; //設(shè)置當前狀態(tài) this.button.onclick = function(){ self.currState.buttonWasPressed(); } } Light.prototype.setState = function(newState){ this.currState = newState; }; var light = new Light(); light.init();
總結(jié)狀態(tài)模式的優(yōu)缺點
狀態(tài)模式定義了狀態(tài)與行為之間的關(guān)系,并將他們封裝在一個類里,通過增加新的狀態(tài),很容易增加新的狀態(tài)和轉(zhuǎn)換。
避免Context無限膨脹,狀態(tài)切換的邏輯被分布在狀態(tài)類中,也去掉了context原本過多的條件分支。
用對象代替字符串來記錄當前的狀態(tài),使得狀態(tài)的切換更加一目了然。
context中的請求動作和狀態(tài)類中封裝的行為可以非常容易的獨立變化而不互相影響。
缺點:會在系統(tǒng)中定義許多的狀態(tài)類,編寫二十個狀態(tài)類是一項枯燥乏味的工作,而且系統(tǒng)中會因此增加不少對象,另外由于邏輯分布在狀態(tài)類中,雖然避開了不受歡迎的條件語句,但是也造成了邏輯分散的問題。我們無法在一個地方就看出整個狀態(tài)機的邏輯。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/100058.html
摘要:訂閱模式的一個典型的應(yīng)用就是后面會寫一篇相關(guān)的讀書筆記。享元模式享元模式的核心思想是對象復用,減少對象數(shù)量,減少內(nèi)存開銷。適配器模式對目標函數(shù)進行數(shù)據(jù)參數(shù)轉(zhuǎn)化,使其符合目標函數(shù)所需要的格式。 設(shè)計模式 單例模式 JS的單例模式有別于傳統(tǒng)面向?qū)ο笳Z言的單例模式,js作為一門無類的語言。使用全局變量的模式來實現(xiàn)單例模式思想。js里面的單例又分為普通單例和惰性單例,惰性單例指的是只有這個實例...
摘要:代理模式,迭代器模式,單例模式,裝飾者模式最少知識原則一個軟件實體應(yīng)當盡可能少地與其他實體發(fā)生相互作用。迭代器模式可以將迭代的過程從業(yè)務(wù)邏輯中分離出來,在使用迭代器模式之后,即不用關(guān)心對象內(nèi)部構(gòu)造也可以按順序訪問其中的每個元素。 接手項目越來越復雜的時候,有時寫完一段代碼,總感覺代碼還有優(yōu)化的空間,卻不知道從何處去下手。設(shè)計模式主要目的是提升代碼可擴展性以及可閱讀性。 本文主要以例子的...
摘要:開發(fā)中,我們或多或少地接觸了設(shè)計模式,但是很多時候不知道自己使用了哪種設(shè)計模式或者說該使用何種設(shè)計模式。本文意在梳理常見設(shè)計模式的特點,從而對它們有比較清晰的認知。 showImg(https://segmentfault.com/img/remote/1460000014919705?w=640&h=280); 開發(fā)中,我們或多或少地接觸了設(shè)計模式,但是很多時候不知道自己使用了哪種設(shè)...
摘要:模式迭代器模式顧名思義,迭代器可以將對于一個聚合對象內(nèi)部元素的訪問與業(yè)務(wù)邏輯分離開。模式組合模式組合模式將對象組合成樹形結(jié)構(gòu),以表示層級結(jié)構(gòu)。重點是,葉結(jié)點與中間結(jié)點有統(tǒng)一借口。本文總結(jié)自設(shè)計模式與開發(fā)實踐,曾探著 模式1 - 單例模式 單例模式的核心是確保只有一個實例,并且提供全局訪問。 特點: 滿足單一職責原則 : 使用代理模式,不在構(gòu)造函數(shù)中判斷是否已經(jīng)創(chuàng)建過該單例; 滿足惰...
摘要:什么時候需要用到單例模式呢其實單例模式在日常開發(fā)中的使用非常的廣泛,例如各種浮窗像登錄浮窗等,無論我們點擊多少次,都是同一個浮窗,浮窗從始至終只創(chuàng)建了一次。這種場景就十分適合運用單例模式。 單例模式 什么是單例模式? 單例模式的定義:一個類僅有一個實例,并且可以在全局訪問。什么時候需要用到單例模式呢?其實單例模式在日常開發(fā)中的使用非常的廣泛,例如各種浮窗、像登錄浮窗等,無論我們點擊多少...
閱讀 3939·2021-11-18 13:19
閱讀 1181·2021-10-11 10:58
閱讀 3291·2019-08-29 16:39
閱讀 3142·2019-08-26 12:08
閱讀 2036·2019-08-26 11:33
閱讀 2460·2019-08-23 18:30
閱讀 1309·2019-08-23 18:21
閱讀 2522·2019-08-23 18:18