什麼是設計模式?
設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結,設計模式並不是一種固定的公式,而是一種思想,是一種解決問題的思路;使用設計模式是為了可重用程式碼,讓程式碼更容易被他人理解,保證程式碼可維護性。
設計模式不區分程式語言,設計模式是解決通用問題和提效的解決方案;通常在我們解決問題的時候,很多時候不是隻有一種方式,我們通常有多種方式來解決;但是肯定會有一種通用且高效的解決方案,這種解決方案在軟體開發中我們稱它為設計模式;
專案中合理的運用設計模式可以完美的解決很多問題,每種模式在現實中都有相應的場景及其原理來與之對應,每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。
什麼是設計原則?
設計原則是基於設計模式產生的一套方法論;通常在做很多事情的時候,都會有一定的規範制約;在軟體開發的過程中,我們可以將設計原則視為一種約定俗成的開發規範,但不是必須要遵循的;
6 個原則的首字母(里氏替換原則和迪米特法則的首字母重複,只取一個)聯合起來就是:SOLID(穩定的),其代表的含義也就是把這 6 個原則結合使用的好處:建立穩定、靈活、健壯的設計;
設計原則
一個類只做一件事;一個類應該只有一個引起它修改的原因,應該只有一個職責。每一個職責都是變化的一個軸線,如果一個類有一個以上的職責,這些職責就耦合在了一起。導致脆弱的設計。例如:要實現邏輯和介面的分離
一個軟體實體如類,模組和函數應該對擴充套件開放,對修改封閉,即在程式需要進行拓展的時候,不能去修改原有的程式碼。
不要破壞繼承體系;程式中的子類應該可以替換父類別出現的任何地方並保持預期不變。所以子類儘量不要改變父類別方法的預期行為。
降低耦合度,一個類或物件應該對其它物件保持最少的瞭解。只與直接的朋友(耦合)通訊,不與朋友的朋友通訊。
設計介面的時候要精簡單一;當類 A 只需要介面 B 中的部分方法時,因為實現介面需要實現其所有的方法,於是就造成了類 A 多出了部分不需要的程式碼。這時應該將 B 介面拆分,將類A需要和不需要的方法隔離開來。
細節應該依賴於抽象 ,抽象不依賴於細節;把抽象層放在程式設計的最高層,並保持穩定,程式的細節變化由低層的實現層來完成。(例如實現一個元件基礎類別,存放元件的id、apperance等資訊,具體的元件類繼承基礎類別,實現具體的UI及功能 )
為什麼要使用設計模式?
設計模式分類
抽象工廠、工廠、單例、建造者、原型
橋接、代理、裝飾器、介面卡、享元、組合、門面(外觀)
觀察者、模板、迭代、狀態、命令、中介者、直譯器、職責鏈、存取者、策略、備忘錄
日常工作常用的設計模式
釋出-訂閱模式(觀察者模式):
定義:
釋出—訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知;釋出訂閱模式有釋出訂閱排程中心(中間商),觀察者模式沒有!
應用場景:
DOM元素節點事件實時觸發方法
vue的響應式原理
大白話解釋(生活中的場景):
假設你在淘寶上看上了某件商品,臨近雙11,但是價格有點高;於是你訂閱了某種型別某種規格的商品降價通知,到雙11降價的時候會系統推播相關訊息給對應的使用者;這時釋出-訂閱模式就產生了;使用者是訂閱者,淘寶是排程中心,商家是釋出者;商家降價之後,會通過釋出中心推播相關的訊息給指定的訂閱者;
1 // 定義一個釋出者物件 2 var pub = { 3 // 快取列表,存放訂閱者回撥函數 4 list: {}, 5 subscribe: function (key, fn) { 6 // 如果沒有該訊息的快取列表,就建立一個空陣列 7 if (!this.list[key]) { 8 this.list[key] = []; 9 } 10 // 將回撥函數推入該訊息的快取列表 11 this.list[key].push(fn); 12 }, 13 14 // 取消訂閱方法 15 unsubscribe: function (key, fn) { 16 // 如果有該訊息的快取列表 17 if (this.list[key]) { 18 // 遍歷快取列表 19 for (var i = this.list[key].length - 1; i >= 0; i--) { 20 // 如果存在該回撥函數,就從快取列表中刪除 21 if (this.list[key][i] === fn) { 22 this.list[key].splice(i, 1); 23 } 24 } 25 } 26 }, 27 // 釋出方法 28 publish: function () { 29 // 獲取訊息型別 30 var key = Array.prototype.shift.call(arguments); 31 // 獲取該訊息的快取列表 32 var fns = this.list[key]; 33 // 如果沒有訂閱訊息,就返回 34 if (!fns || fns.length === 0) { 35 return; 36 } 37 // 遍歷快取列表,執行回撥函數 38 for (var i = 0; i < fns.length; i++) { 39 fns[i].apply(this, arguments); 40 } 41 } 42 } 43 44 // 定義一個訂閱者物件A 45 var subA = function (name) { 46 console.log('A收到了訊息:' + name); 47 } 48 49 // 定義一個訂閱者物件B 50 var subB = function (name) { 51 console.log('B收到了訊息:' + name); 52 } 53 54 // A訂閱了test訊息 55 pub.subscribe('test', subA); 56 // B訂閱了test訊息 57 pub.subscribe('test', subB); 58 // 釋出了test訊息,傳遞了引數'hello' 59 pub.publish('test', 'hello'); 60 // A取消訂閱了test訊息 61 pub.unsubscribe('test', subA); 62 // 釋出了test訊息,傳遞了引數'world' 63 pub.publish('test', 'world'); 64 65 // 輸出: 66 // A收到了訊息: hello 67 // B收到了訊息: hello 68 // A取消訂閱了test訊息
代理模式:
定義:
為一個物件提供一個代用品或預留位置,以便控制對它的存取!
應用場景:
Proxy代理物件;
圖片預載入,佔位loading圖片就是代理;
大白話解釋(生活中的場景):
明星往往擁有很多粉絲,也會接收到很多粉絲送的禮物;但是這些禮物一般都是通過 粉絲 -> 經紀人 -> 明星 這個流程才到達明星的手裡;這裡的經紀人就是明星的代理物件,有些無用或者惡作劇之類的禮物,可以直接在代理物件(經紀人)這一層直接攔截或者過濾掉;
1 var fans = { 2 flower() { 3 agent.reception('花'); 4 } 5 } 6 7 var agent = { 8 reception: function (gift) { 9 console.log('粉絲送的:' + gift); // 粉絲送的:花 10 if (gift !== '花') { 11 star.reception('花'); 12 } 13 } 14 } 15 16 var star = { 17 reception: function (gift) { 18 console.log('收到粉絲的:' + gift); 19 } 20 } 21 22 fans.flower();
策略模式:
定義:
定義一系列演演算法,並將這些演演算法各自封裝成策略類(方法),然後將不變的部分和變化的部分分離開來,並且這些演演算法可以相互替換
應用場景:
主要用來消除或減少邏輯分支判斷,避免冗長的if-else或switch分支判斷
大白話解釋(生活中的場景):
政府事務辦理中心,是近幾年來提升居民、市民辦事的一項策略調整;在政務中心沒出現之前,市民辦事都需要去對應的政府機關視窗分流辦理相應手續;政務大廳的出現將公安、財務、勞動、稅務、供電等多個部門事務辦理手續集中到政務中心來辦理,這樣提高了各級機關單位的辦事效率,減少了維護辦事人群的秩序,也提升了市民的使用者體驗;市民只需要記住辦理政務相關手續去政務大廳即可;
1 /** 策略模式改造前 */ 2 function calculateBonus(level,salary){ 3 if(level === 'S'){ 4 return salary*4; 5 } 6 7 if(level === 'A'){ 8 return salary*3 9 } 10 11 if(level === 'B'){ 12 return salary*2 13 } 14 } 15 16 console.log(calculateBonus("S",14000)); //56000 17 console.log(calculateBonus("A",10000)); //30000 18 console.log(calculateBonus("B",5000)); //10000
1 /** 策略模式改造後*/ 2 var strategies = { 3 "S":function(salary){ 4 return salary*4 5 }, 6 "A":function(salary){ 7 return salary*3; 8 }, 9 "B":function(salary){ 10 return salary*2 11 } 12 } 13 14 var calculateBonus =function(level,salary){ 15 return strategies[level](salary); 16 } 17 console.log(calculateBonus("S",14000)); //56000 18 console.log(calculateBonus("A",10000)); //30000 19 console.log(calculateBonus("B",5000)); //10000
單例模式
定義:
保證一個類僅有一個範例,並提供一個存取它的全域性存取點。
應用場景:
彈窗元件;
載入某個script資源或者只能存在一個全域性變數;
React狀態管理工具Redux
大白話解釋(生活中的場景):
假設甲去當地派出所辦理身份證相關手續,這時公安機關會對甲的身份進行驗證查詢,如果甲已經有過身份登記資訊,就直接辦理掛失補辦手續;否則走新增戶籍人手續;一個合法公民不可能同時存在兩張合法身份證件;
1 let Singleton = function (name) { 2 this.name = name; 3 this.instance = null; 4 } 5 6 Singleton.prototype.getName = function () { 7 console.log(this.name); 8 } 9 10 Singleton.getInstance = function (name) { 11 if (this.instance) { 12 return this.instance; 13 } 14 return this.instance = new Singleton(name); 15 } 16 17 let Winner = Singleton.getInstance('Winner'); 18 let Looser = Singleton.getInstance('Looser'); 19 20 console.log(Winner === Looser); // true 21 console.log(Winner.getName()); // 'Winner' 22 console.log(Looser.getName()); // 'Winner'
總結