摘要:命令模式的由來,其實是回調(diào)函數(shù)的一個面向?qū)ο蟮奶娲罚钅J皆缫讶谌氲搅苏Z言之中。
創(chuàng)建型模式是對某情景下,針對某種問題的某種解決方案。而一個設(shè)計模式是用來解決一個經(jīng)常出現(xiàn)的設(shè)計問題的經(jīng)驗方法。這么說來,每個模式都可能有著自己的意圖,應(yīng)用場景,使用方法和使用后果。本文的行文思路和目的皆在于了解各個模式的定義,應(yīng)用場景和用實例說明如何在前端開發(fā)中使用。
本文所設(shè)計到的概念和實例大多來自《Head First設(shè)計模式》和《JavaScript設(shè)計模式和開發(fā)實踐》二書,前者以生動形象的例子和簡明幽默的句子闡述了何為設(shè)計模式,鑒于JavaScript語言的特殊性,后者以實例說明了在JavaScript中如何應(yīng)用設(shè)計模式,兩本都是我讀后收獲非常大的書。
關(guān)于模式的分類,是為了建立起模式之間的關(guān)系。本文采用最廣為人知的分類:創(chuàng)建型、行為型、結(jié)構(gòu)型來敘述。本文只涉及到部分模式,在之后的學(xué)習(xí)過程中,本人還好不斷修改和補充。
“模式只是指導(dǎo)方針,實際工作中,可以改變模式來適應(yīng)實際問題。”
策略模式(Strategy) 定義將對象實例化,這類模式都提供一個方法,將客戶從所需要的實例化的對象中解耦。
策略模式定義了算法組,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶。
使用場景要達到某一個目的,根據(jù)具體的實際情況,選擇合適的方法。適合于實現(xiàn)某一個功能有多種方案可以選擇的情景。
實現(xiàn)策略類的組成:
一組策略類,策略類封裝了具體的算法,并負責(zé)具體的計算過程;
環(huán)境類:負責(zé)接收客戶的請求,并把請求委托給某一個策略類;
一個按不同等級計算年終獎的例子
// 策略組 var strategies = { "S": function(salary){ return salary * 4; }, "A": function(salary){ return salary * 3; }, "B":function(salary){ return salary * 2 } }; // 內(nèi)容組 var calculateBonus = function(level,salary){ return strategies[level](salary); } // 執(zhí)行 console.log(calculateBonus("S",20000)); // 輸出:80000 console.log(calculateBonus("A",10000)); // 輸出:30000單件模式(Singleton) 定義
單件模式確保一個類只有一個實例,并提供一個全局訪問點。
使用場景用于創(chuàng)建獨一無二的,只能有一個實例的對象,單件模式給了我們一個全局的訪問點,和全局變量一樣方便又沒有全局變量的缺點。
實現(xiàn)沒有公開的構(gòu)造器,利用延遲實例化的方式來創(chuàng)建單件,這種做法對資源敏感的對象特別重要。
傳統(tǒng)語言的實現(xiàn):
而對JavaScript而言,并無類的概念,因此要實現(xiàn)它的核心,確保只有一個實例并提供全局訪問。但是把全局變量當(dāng)成單例來使用容易造成命名污染。
防止命名空間污染的方法:
使用命名空間
使用閉包封裝私有變量
JavaScript惰性單例
惰性單例指的是在需要的時候才創(chuàng)建對象單例。
代碼示例:
// 單例模式 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); } var createSingleLoginLayer = getSingle(createLoginLayer);工廠模式(Factory) 定義
工廠方法模式定義了一個創(chuàng)建對象的接口,但由子類決定要實例化的類是哪一個,工廠方法讓類把實例化推遲到子類。
創(chuàng)建新對象,且該對象需要被被封裝。
工廠模式通過讓子類來決定該創(chuàng)建的對象是什么,來達到將對象創(chuàng)建的過程封裝的目的。
創(chuàng)建對象的方法使用的是繼承,用于創(chuàng)建一個產(chǎn)品的實例;
抽象工廠模式(Abstract Factory) 定義提供一個借口,用于創(chuàng)建相關(guān)或依賴對象的家族,而不需要明確指定具體類。
定義一個負責(zé)創(chuàng)建一組產(chǎn)品的接口,這個接口內(nèi)的每一個方法都負責(zé)創(chuàng)建一個具體產(chǎn)品。抽象工廠的方法通常以工廠方法的方式實現(xiàn)。
創(chuàng)建對象的方法使用的是組合,把一群相關(guān)的產(chǎn)品集合起來,類似于工廠里有一個個的車間。用于創(chuàng)建一組產(chǎn)品。
行為型模板模式(Template) 定義類和對象如何交互和分配職責(zé)
在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。模板就是一個方法,這個方法將算法定義為一個步驟,其中的任何步驟都可以是抽象的,由子類負責(zé)實現(xiàn)。
使用場景適用于算法的結(jié)構(gòu)保持不變,同時由子類提供部分實現(xiàn)的情況。常被架構(gòu)師用于搭建項目的框架,架構(gòu)師定好了骨架,程序員繼承了骨架的結(jié)構(gòu)之后,負責(zé)往里面填空。
鉤子是一種被聲明在抽象類中的方法,只有空的或默認的實現(xiàn)。鉤子的存在,可以讓子類有能力對算法的不同點進行掛鉤。要不要掛鉤,由子類決定(可選)。在容易變化的地方放置鉤子,鉤子可以有一個默認的實現(xiàn),但是究竟要不要“掛鉤”,這由子類自行決定。
實現(xiàn)一個經(jīng)典的coffee or tea的例子
// 創(chuàng)建抽象父類 var Beverage = function(){}; Beverage.prototype.boilWater = function(){ console.log("把水煮沸"); }; // 三個空方法,由子類實現(xiàn) Beverage.prototype.brew = function(){}; Beverage.prototype.pourIncup = function(){}; Beverage.prototype.addCondimwnts = function(){}; // 實現(xiàn)順序 Beverage.prototype.init = function(){ this.boilWater(); this.brew(); this.pourInCup(); this.addCondiments(); }; // 實現(xiàn)煮咖啡 var Coffee = function(){}; Coffee.prototype = new Beverage(); Coffee.prototype.brew =function(){ console.log("煮咖啡"); }; Coffee.prototype.pourIncup = function(){ console.log("coffee倒入杯子"); }; Coffee.prototype.addCondiments = function(){ console.log("加糖和牛奶"); }; var coffee = new Coffee(); coffee.init(); // 實現(xiàn)怕茶 var Tea = function(){}; Tea.prototype = new Beverage(); Tea.prototype.brew =function(){ console.log("泡茶"); }; Tea.prototype.pourIncup = function(){ console.log("tea倒入杯子"); }; Tea.prototype.addCondiments = function(){ console.log("加檸檬"); }; var tea = new Tea(); tea.init();命令模式(Command) 定義
命令模式將請求封裝成對象,以便使用不同的請求、隊列或者日志來參數(shù)化其他對象,命令模式也支持可撤銷的操作。
使用場景有時候需要向某些對象發(fā)送請求,但是并不知道請求的接受者是誰,也不知道請求的操作是什么,將‘對象的請求者‘從’命令的執(zhí)行者’中解耦。使用此模式的優(yōu)點還在于,command對象擁有更長的生命周期,可以在程序運行的任何時刻去調(diào)用這個方法。
實現(xiàn)命令模式將動作和接受者包進對象中。這個對象只暴露出一個execute()方法,當(dāng)此方法被調(diào)用的時候,接受者就會進行這些動作。從外面來看,其它對象不知道究竟哪個接受者進行了這些動作,只知道如果調(diào)用execute()方法,請求目的就達到了。
命令模式的由來,其實是回調(diào)函數(shù)的一個面向?qū)ο蟮奶娲罚钅J皆缫讶谌氲搅薐avaScript語言之中。
// 命令模式 // 具體的命令執(zhí)行動作(廚師炒菜) var MenuBar = { refresh:function(){ console.log("刷新菜單界面") } } // 傳遞命令(把菜單給廚師) var RefreshMenuBarCommand = function(receiver){ return{ execute:function(){ receiver.refresh(); } } } // 可見的命令(菜單) var setCommand = function(button,command){ button.onclick = function(){ command.execute() } } // 請求命令(點餐) var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar); // 執(zhí)行命令(在顧客不可見的情況下,廚師炒菜) setCommand(button1,refreshMenuBarCommand)迭代器模式(Iterator) 定義
迭代器模式提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露其內(nèi)部的表示,有內(nèi)部迭代器和外部迭代器之分,其中內(nèi)部迭代器全接手整個迭代過程,外部只需要一次初始調(diào)用,而外部迭代器必須顯式的請求下一個元素。
使用場景需要順序訪問一個組合內(nèi)的多個對象的時候使用。
實現(xiàn)一個對比對象的例子
var Iterator = function(obj){ var current = 0; var next = function(){ current + = 1; }; var isDone = function(){ return current >=obj.length; }; var getCurrItem = function(){ return obj[current]; }; return{ next:next, isDone:isDone, getCurrItem:getCurrItem } } var compare = function(iterator1,iterator2){ while(!iterator1.isDone() && !iterator2.isDone()){ if (iterator1.getCurrItem() !== iterator2.getCurrItem()) { throw new Error("iteraor1和iteraor2不相等"); } iterator1.next(); iterator2.next(); } alert("二者相等"); } var iterator1 = Iterator([1,2,3]); var iterator2 = Iterator([1,2,3]); compare(iterator1,iterator2);觀察者模式(Observer) 定義
又稱發(fā)布-訂閱模式,定義了對象之間的一對多依賴,這樣一來,當(dāng)一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并自動更新。
使用場景幫你的對象知悉現(xiàn)狀,不會錯過該對象感興趣的事情,對象甚至可以在運行時決定是否需要繼續(xù)被通知,就像你關(guān)注了京東商城某款產(chǎn)品的降價信息,當(dāng)該商品降價,你就會通過短信或者郵件獲得通知,而不用你每天都登陸去看了,這種情況下,京東商城就是主題(subject),作為客戶的你就是觀察者了。
主題是具有狀態(tài)的對象,并且可以控制這些狀態(tài);
觀察者使用這些狀態(tài),雖然這些狀態(tài)不屬于它們;
主題和觀察者之間數(shù)據(jù)的傳輸有推(push)和拉(pull)兩種,推得方式被認為更加正確;
廣泛應(yīng)用在異步編程中;
二者之間通過松耦合聯(lián)系在一起;
實現(xiàn)指定好主題(發(fā)布者);
給主題一個緩存列表,用于存放回調(diào)函數(shù)以便通知觀察者;
發(fā)布消息時,主題遍歷緩存列表,觸發(fā)里面存放的訂閱者回調(diào)函數(shù);
訂閱者接受信息,各自處理;
一個獲取房價信息變化的例子
var salesOffice = {}; //定義售樓處 salesOffice.clienList = []; //緩存列表,存放訂閱者的回調(diào)函數(shù) // 注冊為觀察者 salesOffice.listen = function(key,fn){ if (!this.clienList[key]) { this.clienList[key]=[]; // 如果還沒有訂閱過此消息,給該類消息訂閱一個緩存列表 } this.clienList[key].push(fn); //訂閱的消息添加進消息緩存列表 }; // 不再觀察 salesOffice.remove = function(key,fn){ var fns = this.clienList[key]; if (!fns) { return false; // 無人關(guān)注此類消息,直接返回; } if (!fn) { fns&&(fns.length = 0 ); // 沒有傳入具體的回調(diào)函數(shù),表示需要取消key對應(yīng)消息的所有訂閱 }else{ for ( var l = fns.length-1; l >=0;l--){ var _fn = fns[l]; if (_fn===fn) { fns.splice(l,1); // 刪除對應(yīng)訂閱 } } } }; // 通知函數(shù) salesOffice.trigger = function(){ // 發(fā)布消息 var key = Array.prototype.shift.call(arguments), // 取出消息類型 fns = this.clienList[key]; // 取出該消息對應(yīng)的函數(shù)集合 if (!fns || fns.length === 0) { return false; // 如果沒有訂閱,則返回 } for(var i = 0 , fn; i狀態(tài)模式(State) 定義 允許對象在內(nèi)部狀態(tài)改變時改變它的行為,對象好像看起來修改了它的類。
使用場景解決某些需要場景的問題。
實現(xiàn)將狀態(tài)封裝為獨立的類,并將請求委托給當(dāng)前的狀態(tài)對象,當(dāng)對象的內(nèi)部狀態(tài)改變時,會帶來不同的行為變化;
不同的狀態(tài)下有不同的行為;
狀態(tài)模式的關(guān)鍵是把事物的每種狀態(tài)封裝為多帶帶的類,跟狀態(tài)有關(guān)的行為被封裝在這個類的內(nèi)部。
var light = function(){ this,currState = FSM.off;//設(shè)計默認狀態(tài) this.button = null; }; 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.currState.buttonWasPress.call(self); } }; var FSM = { off:{ buttonWasPress:function(){ console.log("關(guān)燈"); this.button.innerHTML = "下一次按我是開燈"; this.currState = FSM.on; } }, on:{ buttonWasPress:function(){ console.log("開燈"); this.button.innerHTML = "下一次點擊是關(guān)燈"; this.currState = FSM.off; } } }; var light = new Light(); light.init();結(jié)構(gòu)型裝飾者模式(Decorator) 定義把類和對象組合到更大的結(jié)構(gòu)中
動態(tài)的將責(zé)任附加到對象上。它比繼承更具有彈性。
缺點:
在設(shè)計中加入大量的小類,導(dǎo)致別人不理解設(shè)計方式;
類型問題;
增加代碼的復(fù)雜度
使用場景增加行為到包裝對象上,在不改變對象自身的基礎(chǔ)上,在程序運行期間給對象動態(tài)的添加職責(zé),比如說點了一杯咖啡,添加其它調(diào)料的過程,或者類似于在炒菜的過程中,加油加鹽加料酒的過程。
實現(xiàn)裝飾者和被裝飾者具有一樣的類型,也就是有共同的超類;
新的行為由組合對象得到;
行為來自裝飾者和基礎(chǔ)組件,或與其它裝飾者之間的組合關(guān)系;
一個沖咖啡的例子
// 被裝飾者 var coffee = function(){ make:function(){ console.log("沖咖啡"); } } //裝飾者1 var sugerDecorator = function(){ console.log("加糖"); } // 裝飾者2 var milkDecorator = function(){ console.log("加奶"); } var coffee1 = coffee.make; coffee.make = function(){ coffee1(); sugerDecorator(); } var coffee2 = coffee.make; coffee.make = function(){ coffee2(); milkDecorator(); } coffee.make(); // 沖咖啡加糖加奶代理模式(Proxy) 定義代理模式為另一個對象提供一個替身或占位符以控制對這個對象的訪問
使用場景使用代理模式創(chuàng)建對象,讓代表對象控制某對象的訪問,被代理的對象可以是遠程的對象,創(chuàng)建開銷大的對象或者需要安全控制的對象。
保護代理用于過濾掉一些請求;
虛擬代理把一些開銷大的請求延遲到真正需要它的時候才去創(chuàng)建(最常用);
使用方法類圖
一個圖片預(yù)加載的例子
var myImage = (function(){ var imgNode = document.createElement("img"); document.body.appendChild(imgNode); return{ setSrc:function(src){ imgNode.src = src; } } })(); 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;//.../123.jpg");外觀模式(Facade) 定義提供了一個統(tǒng)一的接口
適合場景通過實現(xiàn)一個提供更合理的接口的外觀類,可以將一個復(fù)雜的子系統(tǒng)變得容易使用,不僅簡化了接口,也將客戶從組件中解耦。
適配器模式(Adapter) 定義又名包裝器,適配器模式將一個類的接口,轉(zhuǎn)換為客戶期望的另一個接口,適配器讓原本接口不兼容的類可以合作無間。
類圖
適應(yīng)場景包裝某些對象,讓它們的接口看起來不像自己而像是被的東西,將類的接口轉(zhuǎn)為想要的接口,以便實現(xiàn)不同的接口;就像你買了港版手機,附帶的港版的充電器,你需要一個轉(zhuǎn)接頭才能使用,這個轉(zhuǎn)接頭的功能就類似于適配器。
值得注意的是這是一種亡羊補牢的措施。
實現(xiàn)客戶通過目標(biāo)接口調(diào)用適配器的方法對適配器發(fā)出請求;
適配器使用被適配者接口把請求轉(zhuǎn)換為被被適配者的一個或多個接口;
客戶接受到調(diào)用的結(jié)果,但是并未察覺這一切是適配器在起作用。
對象適配器類圖
類適配器類圖
一個適配器實例
// 適配器模式 var googleMap = { show:function(){ console.log("開始渲染谷歌地圖") } }; var baiduMap = { display:function(){ console.log("開始渲染百度地圖") } }; var baidumapAdapter = { show : function(){ return baiduMap.display(); } }; renderMap(googleMap); renderMap(baiduMapAdapter);說明本文由zhangwang首發(fā)于簡書和segmentfault,轉(zhuǎn)載請加以說明。
參考書籍《Head First設(shè)計模式》
《JavaScript設(shè)計模式和開發(fā)實踐》
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/79843.html
摘要:它每一行代碼都凝結(jié)著我從深坑中跳出來之后的思考,是下文介紹了所有問題和場景的解決方案。在版本推出了新的,這也是所官方推薦的一種跨傳遞數(shù)據(jù)的解決方案。 干貨高能預(yù)警,此文章信息量巨大,大部分內(nèi)容為對現(xiàn)狀問題的思考和現(xiàn)有技術(shù)的論證。 感興趣的朋友可以先收藏,然后慢慢研讀。此文凝結(jié)了我在中臺領(lǐng)域所有的思考和探索,相信讀完此文,能夠讓你對中臺領(lǐng)域的常見業(yè)務(wù)場景和解決方法有著全新的認知。 此文轉(zhuǎn)載請...
摘要:高性能代碼的最佳實踐前言在這篇文章中,我們將討論幾個有助于提升應(yīng)用程序性能的方法。要獲得有關(guān)應(yīng)用程序需求的最好最可靠的方法是對應(yīng)用程序執(zhí)行實際的負載測試,并在運行時跟蹤性能指標(biāo)。 showImg(https://segmentfault.com/img/bVbtgk4?w=256&h=254); 高性能Java代碼的最佳實踐前言 在這篇文章中,我們將討論幾個有助于提升Java應(yīng)用程序性...
摘要:尤其是遇到二次確認等場景因此,打算從頭整理移動彈窗的基礎(chǔ)知識,以彈窗體系為切入點,從定義出發(fā),對移動彈窗進行分類,然后分別分析每一類彈窗的應(yīng)用場景,以及在使用過程中需要注意的點。 摘要: 最為常見的【彈窗】反而是最捉摸不定的東西。各種類型的彈窗傻傻分不清楚,不知道在什么場景下應(yīng)該用哪種彈窗。尤其是遇到二次確認等場景…… 因此,打算從頭整理移動彈窗的基礎(chǔ)知識,以iOS彈窗體系為切入點,從...
摘要:消息隊列技術(shù)介紹后端掘金一消息隊列概述消息隊列中間件是分布式系統(tǒng)中重要的組件,主要解決應(yīng)用耦合異步消息流量削鋒等問題。的內(nèi)存優(yōu)化后端掘金聲明本文內(nèi)容來自開發(fā)與運維一書第八章,如轉(zhuǎn)載請聲明。 消息隊列技術(shù)介紹 - 后端 - 掘金一、 消息隊列概述 消息隊列中間件是分布式系統(tǒng)中重要的組件,主要解決應(yīng)用耦合、異步消息、流量削鋒等問題。實現(xiàn)高性能、高可用、可伸縮和最終一致性架構(gòu)。是大型分布式系...
摘要:我們今天也來做一個萬能遙控器設(shè)計模式適配器模式將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口。今天要介紹的仍然是創(chuàng)建型設(shè)計模式的一種建造者模式。設(shè)計模式的理論知識固然重要,但 計算機程序的思維邏輯 (54) - 剖析 Collections - 設(shè)計模式 上節(jié)我們提到,類 Collections 中大概有兩類功能,第一類是對容器接口對象進行操作,第二類是返回一個容器接口對象,上節(jié)我們介紹了...
閱讀 1275·2023-04-25 23:22
閱讀 1675·2023-04-25 20:04
閱讀 2650·2021-11-22 15:24
閱讀 2811·2021-11-11 16:54
閱讀 1891·2019-08-30 14:03
閱讀 1490·2019-08-29 16:35
閱讀 1708·2019-08-26 10:29
閱讀 2670·2019-08-23 18:01