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