前端常用設計模式

有夢想的鹹魚前端發表於2023-11-07

什麼是設計模式?

​  設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結,設計模式並不是一種固定的公式,而是一種思想,是一種解決問題的思路;使用設計模式是為了可重用程式碼,讓程式碼更容易被他人理解,保證程式碼可維護性。

  設計模式不區分程式語言,設計模式是解決通用問題和提效的解決方案;通常在我們解決問題的時候,很多時候不是隻有一種方式,我們通常有多種方式來解決;但是肯定會有一種通用且高效的解決方案,這種解決方案在軟體開發中我們稱它為設計模式;

  專案中合理的運用設計模式可以完美的解決很多問題,每種模式在現實中都有相應的場景及其原理來與之對應,每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。

什麼是設計原則?

  設計原則是基於設計模式產生的一套方法論;通常在做很多事情的時候,都會有一定的規範制約;在軟體開發的過程中,我們可以將設計原則視為一種約定俗成的開發規範,但不是必須要遵循的;

  • 單一職責原則(Single Responsibility Principle)
  • 開閉原則(Open Closed Principle)
  • 里氏替換原則(Liskov Substitution Principle)
  • 迪米特法則(Law of Demeter)
  • 介面隔離原則(Interface Segregation Principle)
  • 依賴倒置原則(Dependence Inversion Principle)

  6 個原則的首字母(里氏替換原則和迪米特法則的首字母重複,只取一個)聯合起來就是:SOLID(穩定的),其代表的含義也就是把這 6 個原則結合使用的好處:建立穩定、靈活、健壯的設計;

設計原則

  • 單一職責原則(Single Responsibility Principle)

    一個類只做一件事;一個類應該只有一個引起它修改的原因,應該只有一個職責。每一個職責都是變化的一個軸線,如果一個類有一個以上的職責,這些職責就耦合在了一起。導致脆弱的設計。例如:要實現邏輯和介面的分離

  • 開閉原則(Open Closed Principle)

   一個軟體實體如類,模組和函式應該對擴充套件開放,對修改封閉,即在程式需要進行擴充的時候,不能去修改原有的程式碼。

  • 里氏替換原則(Liskov Substitution Principle)

   不要破壞繼承體系;程式中的子類應該可以替換父類出現的任何地方並保持預期不變。所以子類儘量不要改變父類方法的預期行為。

  • 迪米特法則(Law of Demeter)

   降低耦合度,一個類或物件應該對其它物件保持最少的瞭解。只與直接的朋友(耦合)通訊,不與朋友的朋友通訊。

  • 介面隔離原則(Interface Segregation Principle)

   設計介面的時候要精簡單一;當類 A 只需要介面 B 中的部分方法時,因為實現介面需要實現其所有的方法,於是就造成了類 A 多出了部分不需要的程式碼。這時應該將 B 介面拆分,將類A需要和不需要的方法隔離開來。

  • 依賴倒置原則(Dependence Inversion Principle)

   細節應該依賴於抽象 ,抽象不依賴於細節;把抽象層放在程式設計的最高層,並保持穩定,程式的細節變化由低層的實現層來完成。(例如實現一個元件基類,存放元件的id、apperance等資訊,具體的元件類繼承基類,實現具體的UI及功能 )

為什麼要使用設計模式?

  1. 設計模式是前人根據經驗總結出來的,使用設計模式,就相當於是站在了前人的肩膀上。
  2. 設計模式使程式易讀。熟悉設計模式的人應該能夠很容易讀懂運用設計模式編寫的程式。
  3. 設計模式能使編寫的程式具有良好的可擴充套件性,滿足系統設計的開閉原則。
  4. 設計模式能降低系統中類與類(或者function與function)之間的耦合度。
  5. 設計模式能提高程式碼的重用度。
  6. 設計模式能為常見的一些問題提供現成的解決方案。
  7. 設計模式增加了重用程式碼的方式。比如裝飾器模式,在不使用繼承的前提下重用系統中已存在的程式碼。

設計模式分類

  •  5種建立型
  •  7種結構型
  •  11種行為型
  • 建立型:抽工單建原型

   抽象工廠、工廠、單例、建造者、原型

  •  結構型:橋代理裝飾介面卡,享元組合成門面

      橋接、代理、裝飾器、介面卡、享元、組合、門面(外觀)

  •  行為型:觀察模板迭代的狀態,命令中介解釋職責鏈,訪問策略備忘錄

   觀察者、模板、迭代、狀態、命令、中介者、直譯器、職責鏈、訪問者、策略、備忘錄

日常工作常用的設計模式

  釋出-訂閱模式(觀察者模式):

  定義:

    釋出—訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知;釋出訂閱模式有釋出訂閱排程中心(中間商),觀察者模式沒有!

  應用場景:

  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'

  總結

  1. 在日常開發中,應該儘可能的遵守設計原則和合理選用設計模式;
  2. 合理使用設計模式便於我們寫出可維護性高、擴充性強的程式碼;
  3. 設計模式一共分為三大類,分別是 :5種建立型、7種結構型、11種行為型;
  4. 設計模式不區分程式語言,設計模式是解決通用問題和提效的解決方案;
  5. 程式設計的目的是解決現實問題,同樣每一種設計模式的原理都能在現實社會中找到對應的場景,這也是它能被廣泛應用的根本原因;