今天主要介紹一下咱們日常會常常用到的設計模式,設計模式總的來講有23種,而設計模式在前端中又該怎麼運用呢,接下來主要對比較前端中常見的設計模式作一個介紹前端
1、什麼是設計模式vue
通常來講,設計模式表明了最佳的實踐,一般被有經驗的面向對象的軟件開發人員所採用,在咱們平時的軟件開發中,常常須要用到各類設計模式,設計模式是一套被反覆使用的、多數人知曉的、通過分類編目的、代碼設計經驗的總結,使用設計模式是爲了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。react
設計模式能夠說是軟件工程的基石,合理的使用設計模式,可使咱們的代碼真正的工程化,在項目中使用設計模式能夠完美的解決不少問題,在設計模式中,大概來講總共有23種,而具體要用哪種還須要根據狀況而定,就像平時在前端開發中,我比較熟悉的就是工廠模式,原型模式和MVC這些模式啦,接下來主要對其中的一些設計模式進行一個比較詳細的介紹。算法
2、設計模式的分類編程
首先,仍是須要先說一下設計模式的分類,剛纔說到設計模式總的來講有23種,而這23種,又能夠分爲如下四大類設計模式
一、建立型模式安全
建立型模式提供了一種在建立對象的同時隱藏建立邏輯的方式,而不是使用 new 運算符直接實例化對象,這使得程序在判斷針對某個給定實例須要建立哪些對象時更加靈活,主要包括如下幾種:閉包
工廠模式、抽象工廠模式、單例模式、建造者模式、原型模式 架構
二、結構型模式框架
結構型模式關注類和對象的組合繼承的概念被用來組合接口和定義組合對象得到新功能的方式,主要包括如下幾種:
適配器模式、橋接模式、過濾器模式、組合模式、裝飾器模式、外觀模式、享元模式、代理模式
三、行爲型模式
行爲型模式關注對象之間的通訊,主要包括如下幾種:
責任鏈模式、命令模式、解釋器模式、迭代器模式、中介者模式、備忘錄模式、觀察者模式、狀態模式、空對象模式、策略模式、模板模式、訪問者模式
四、J2EE模式
J2EE模式關注表示層,這些模式是由 Sun Java Center 鑑定的,主要包括如下幾種:
MVC 模式、業務表明模式、組合實體模式、數據訪問對象模式、前端控制器模式、攔截過濾器模式、服務定位器模式、傳輸對象模式
3、設計模式六大原則
上面介紹了幾種不一樣設計的模式,而全部的設計模式都須要遵循下面的六大原則
一、開閉原則
開閉原則的意思是:對擴展開放,對修改關閉,在程序須要進行拓展的時候,不能去修改原有的代碼
二、里氏代換原則
里氏代換原則中說,任何基類能夠出現的地方,子類必定能夠出現,里氏代換原則是對開閉原則的補充,實現開閉原則的關鍵步驟就是抽象化,而基類與子類的繼承關係就是抽象化的具體實現,因此里氏代換原則是對實現抽象化的具體步驟的規範
三、依賴倒轉原則
這個原則是開閉原則的基礎,具體內容:針對接口編程,依賴於抽象而不依賴於具體
四、接口隔離原則
這個原則的意思是:使用多個隔離的接口,比使用單個接口要好,它還有另一個意思是:下降類之間的耦合度,此可見,其實設計模式就是從大型軟件架構出發、便於升級和維護的軟件設計思想,它強調下降依賴,下降耦合
五、最少知道原則
最少知道原則是指:一個實體應當儘可能少地與其餘實體之間發生相互做用,使得系統功能模塊相對獨立
六、合成複用原則
合成複用原則是指:儘可能使用合成/聚合的方式,而不是使用繼承
4、常見的設計模式
設計模式有不少種,接下來,我將介紹其中的幾種,而且介紹這些設計模式怎麼運用在前端中
一、工廠模式
工廠模式是用來建立對象的一種最經常使用的設計模式,咱們不暴露建立對象的具體邏輯,而是將將邏輯封裝在一個函數中,那麼這個函數就能夠被視爲一個工廠,工廠模式根據抽象程度的不一樣能夠分爲:簡單工廠,工廠方法和抽象工廠,接下來,將對簡單工廠和工廠方法在JavaScript中的運用舉個簡單的例子
(1)簡單工廠
簡單工廠模式又叫靜態工廠模式,由一個工廠對象決定建立某一種產品對象類的實例,主要用來建立同一類對象
好比說,在實際的項目中,咱們經常須要根據用戶的權限來渲染不一樣的頁面,高級權限的用戶所擁有的頁面有些是沒法被低級權限的用戶所查看,因此咱們能夠在不一樣權限等級用戶的構造函數中,保存該用戶可以看到的頁面。在根據權限實例化用戶
let UserFactory = function (role) { function SuperAdmin() { this.name = "超級管理員", this.viewPage = ['首頁', '通信錄', '發現頁', '應用數據', '權限管理'] } function Admin() { this.name = "管理員", this.viewPage = ['首頁', '通信錄', '發現頁', '應用數據'] } function NormalUser() { this.name = '普通用戶', this.viewPage = ['首頁', '通信錄', '發現頁'] } switch (role) { case 'superAdmin': return new SuperAdmin(); break; case 'admin': return new Admin(); break; case 'user': return new NormalUser(); break; default: throw new Error('參數錯誤, 可選參數:superAdmin、admin、user'); } } //調用 let superAdmin = UserFactory('superAdmin'); let admin = UserFactory('admin') let normalUser = UserFactory('user')
在上面的例子中,UserFactory
就是一個簡單工廠,在該函數中有3個構造函數分別對應不一樣的權限的用戶,當咱們調用工廠函數時,只須要傳遞superAdmin
, admin
, user
這三個可選參數中的一個獲取對應的實例對象
優勢:簡單工廠的優勢在於,你只須要一個正確的參數,就能夠獲取到你所須要的對象,而無需知道其建立的具體細節
缺點:在函數內包含了全部對象的建立邏輯(構造函數)和判斷邏輯的代碼,每增長新的構造函數還須要修改判斷邏輯代碼,咱們的對象不是上面的3個而是30個或更多時,這個函數會成爲一個龐大的超級函數,便得難以維護,簡單工廠只能做用於建立的對象數量較少,對象的建立邏輯不復雜時使用
(2)工廠方法
工廠方法模式的本意是將實際建立對象的工做推遲到子類中,這樣核心類就變成了抽象類,可是在JavaScript中很難像傳統面向對象那樣去實現建立抽象類,因此在JavaScript中咱們只須要參考它的核心思想便可,咱們能夠將工廠方法看做是一個實例化對象的工廠類
好比說上面的例子,咱們用工廠方法能夠這樣寫,工廠方法咱們只把它看做是一個實例化對象的工廠,它只作實例化對象這一件事情,咱們採用安全模式建立對象
//安全模式建立的工廠方法函數 let UserFactory = function(role) { if(this instanceof UserFactory) { var s = new this[role](); return s; } else { return new UserFactory(role); } } //工廠方法函數的原型中設置全部對象的構造函數 UserFactory.prototype = { SuperAdmin: function() { this.name = "超級管理員", this.viewPage = ['首頁', '通信錄', '發現頁', '應用數據', '權限管理'] }, Admin: function() { this.name = "管理員", this.viewPage = ['首頁', '通信錄', '發現頁', '應用數據'] }, NormalUser: function() { this.name = '普通用戶', this.viewPage = ['首頁', '通信錄', '發現頁'] } } //調用 let superAdmin = UserFactory('SuperAdmin'); let admin = UserFactory('Admin') let normalUser = UserFactory('NormalUser')
在簡單工廠中,若是咱們新增長一個用戶類型,須要修改兩個地方的代碼,一個是增長新的用戶構造函數,一個是在邏輯判斷中增長對新的用戶的判斷,而在抽象工廠方法中,咱們只須要在UserFactory.prototype中添加就能夠啦
二、代理模式
代理模式主要是爲其餘對象提供一種代理以控制對這個對象的訪問,主要解決在直接訪問對象時帶來的問題,好比說:要訪問的對象在遠程的機器上,在面向對象系統中,有些對象因爲某些緣由(好比對象建立開銷很大,或者某些操做須要安全控制,或者須要進程外的訪問),直接訪問會給使用者或者系統結構帶來不少麻煩,咱們能夠在訪問此對象時加上一個對此對象的訪問層
代理模式最基本的形式是對訪問進行控制,代理對象和另外一個對象(本體)實現的是一樣的接口,實際上工做仍是本體在作,它纔是負責執行所分派的任務的那個對象或類,代理對象所作的不外乎節制對本體的訪問,代理對象並不會在另外一對象的基礎上添加方法或修改其方法,也不會簡化那個對象的接口,它實現的接口與本體徹底相同,全部對它進行的方法調用都會被傳遞給本體
(function(){ // 示例代碼 // 目標對象,是真正被代理的對象 function Subject(){} Subject.prototype.request = function(){}; /** * 代理對象 * @param {Object} realSubject [持有被代理的具體的目標對象] */ function Proxy(realSubject){ this.realSubject = readSubject; } Proxy.prototype.request = function(){ this.realSubject.request(); }; }());
在上面的代碼中,Proxy能夠控制對真正被代理對象的一個訪問,在代理模式中,比較常見的就是虛擬代理,虛擬代理用於控制對那種建立開銷很大的本體的訪問,它會把本體的實例化推遲到有方法被調用的時候,好比說,如今咱們假設PublicLibrary的實例化很慢,不能在網頁加載的時候當即完成,咱們能夠爲其建立一個虛擬代理,讓它把PublicLibrary的實例化推遲到必要的時候,好比說咱們在前端中常常用到的圖片懶加載,就能夠用虛擬代理
三、觀察者模式
若是你們學過一些像vue,react這些框架,相信你們對觀察者模式必定很熟悉,如今不少mvvm框架都用到了觀察者模式這個思想,觀察者模式又叫作發佈—訂閱模式,它定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知和更新,觀察者模式提供了一個訂閱模型,其中對象訂閱事件並在發生時獲得通知,這種模式是事件驅動的編程基石,它有利益於良好的面向對象的設計
下面舉個例子,好比咱們給頁面中的一個dom節點綁定一個事件,其實就能夠看作是一種觀察者模式
document.body.addEventListener("click", function() { alert("Hello World") },false ) document.body.click() //模擬用戶點擊
在上面的例子中,須要監聽用戶點擊 document.body 的動做,可是咱們是沒辦法預知用戶將在何時點擊的,所以咱們訂閱了 document.body 的 click 事件,當 body 節點被點擊時,body 節點便會向訂閱者發佈 "Hello World" 消息
四、單例模式
單例模式保證一個類僅有一個實例,並提供一個訪問它的全局訪問點,保證一個類只有一個實例,實現的方法通常是先判斷實例存在與否,若是存在直接返回,若是不存在就建立了再返回,這就確保了一個類只有一個實例對象
下面舉個例子,在js中,咱們可使用閉包來建立實現這種模式
var single = (function(){ var unique; function getInstance(){ // 若是該實例存在,則直接返回,不然就對其實例化 if( unique === undefined ){ unique = new Construct(); } return unique; } function Construct(){ // ... 生成單例的構造函數的代碼 } return { getInstance : getInstance } })();
在上面的代碼中,咱們可使用single.getInstance來獲取到單例,而且每次調用均獲取到同一個單例,在咱們平時的開發中,咱們也常常會用到這種模式,好比當咱們單擊登陸按鈕的時候,頁面中會出現一個登陸框,而這個浮窗是惟一的,不管單擊多少次登陸按鈕,這個浮窗只會被建立一次,所以這個登陸浮窗就適合用單例模式
五、策略模式
策略模式指的是定義一些列的算法,把他們一個個封裝起來,目的就是將算法的使用與算法的實現分離開來,避免多重判斷條件,更具備擴展性
下面也是舉個例子,如今超市有活動,vip爲5折,老客戶3折,普通顧客沒折,計算最後須要支付的金額,若是不使用策略模式,咱們的代碼可能和下面同樣
function Price(personType, price) { //vip 5 折 if (personType == 'vip') { return price * 0.5; } else if (personType == 'old'){ //老客戶 3 折 return price * 0.3; } else { return price; //其餘都全價 } }
在上面的代碼中,咱們須要不少個判斷,若是有不少優惠,咱們又須要添加不少判斷,這裏已經違背了剛纔說的設計模式的六大原則中的開閉原則了,若是使用策略模式,咱們的代碼能夠這樣寫
// 對於vip客戶 function vipPrice() { this.discount = 0.5; } vipPrice.prototype.getPrice = function(price) { return price * this.discount; } // 對於老客戶 function oldPrice() { this.discount = 0.3; } oldPrice.prototype.getPrice = function(price) { return price * this.discount; } // 對於普通客戶 function Price() { this.discount = 1; } Price.prototype.getPrice = function(price) { return price ; } // 上下文,對於客戶端的使用 function Context() { this.name = ''; this.strategy = null; this.price = 0; } Context.prototype.set = function(name, strategy, price) { this.name = name; this.strategy = strategy; this.price = price; } Context.prototype.getResult = function() { console.log(this.name + ' 的結帳價爲: ' + this.strategy.getPrice(this.price)); } var context = new Context(); var vip = new vipPrice(); context.set ('vip客戶', vip, 200); context.getResult(); // vip客戶 的結帳價爲: 100 var old = new oldPrice(); context.set ('老客戶', old, 200); context.getResult(); // 老客戶 的結帳價爲: 60 var Price = new Price(); context.set ('普通客戶', Price, 200); context.getResult(); // 普通客戶 的結帳價爲: 200
在上面的代碼中,經過策略模式,使得客戶的折扣與算法解藕,又使得修改跟擴展能獨立的進行,不影到客戶端或其餘算法的使用
當咱們的代碼中有不少個判斷分支,每個條件分支都會引發該「類」的特定行爲以不一樣的方式做出改變,這個時候就可使用策略模式,能夠改進咱們代碼的質量,也更好的能夠進行單元測試
今天就寫到這裏了,其實還有不少設計模式,在這裏尚未進行總結,你們有空的話也能夠本身去了解