這篇文章只簡單通俗的講單例模式

MarkMan發表於2018-09-06

設計模式得存在讓系統程式碼可重用、可擴充套件、可解耦、更容易被人理解且保證程式碼可靠性。設計模式使程式碼真正工程化。 設計模式是一個龐大而又複雜的體系,單例模式大概是23種設計模式中相對比較簡單的一種。今天我們一步一步來解開它的面紗。

瞭解完高階函式可能加快我們理解設計模式喲?,傳送門 掘金 | GitHub

設計原則

想要透徹的理解設計模式,你必須先知道我們的6大設計原則

單一職責原則

There should never be more than one reason for a class to change.

簡稱SRP,核心定義是應該有且僅有一個原因引起類的變更。

里氏替換原則

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T (如果對每一個型別為S的物件o1,都有型別為T的對 象o2,使得以T定義的所有程式P在所有的物件o1都代換成o2時,程式P的行為沒有發生變 化,那麼型別S是型別T的子型別。)

看起來不是很好理解,白話一點就是子類繼承父類,單獨完全可以執行。只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是,反過來就不行了,有子類出現的地方,父類未必就能適應。

依賴倒置原則

High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions

三層意思:

  1. 高層模組不應該依賴低層模組,兩者都應該依賴其抽象。
  2. 抽象不應該依賴細節。
  3. 細節應該依賴抽象。

白話:引用一個物件,如果這個物件有底層型別,直接引用底層型別

介面隔離原則

Clients should not be forced to depend upon interfaces that they don't use.(客戶端不應該依賴它不需要的介面。)

白話:每一個介面應該是一種角色

迪米特原則

迪米特法則(Law of Demeter,LoD)也稱為最少知識原則(Least Knowledge Principle,LKP):一個物件應該對其他物件有最少的瞭解

白話:一個類應該對自己需要耦合或呼叫的類知道得最少,你(被耦合或呼叫的類)的內部是如何複雜都和我沒關係,那是你的事情,我就知道你提供的這麼多public方法,我就呼叫這麼多,其他的我一概不管

開閉原則

Software entities like classes,modules and functions should be open for extension but closed for modifications.(一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉。)

白話:對擴充套件開放,對修改關閉(這個好理解)

簡單示例

設計原則我們已經瞭解完了,接下來我們進入本文得正題,來搞一搞這個單例模式

核心思想

保證一個類只有一個例項,這是單例模式的核心思路。實現方法是先判斷例項存在與否,如果存在直接返回,如果不存在就建立了再返回,確保一個類只有一個例項物件。在JavaScript中藉助她本身強大的靈活性有多種方式可以實現。單例作為一個名稱空間提供者,從全域性名稱空間裡提供一個唯一的訪問點來訪問該物件。所以我們不難寫出下面這種程式碼

// 單例物件
// 1: 最簡單的單例模式 也被稱為基本單例模式
const singleton = {
  prop:"value",
  method(){
  }
}
//這種形式的單例模式,所有成員都是公開的,都可以通過singleton來訪問。這樣的缺點是單例中有一些輔助的方法並不希望暴露給使用者,如果使用者用了這些方法,然後在後面維護的時候,一些輔助方法被刪除,這樣會造成程式錯誤。

// 2: 藉助閉包我們可以輕易建立一個單例模式 也被稱為惰性載入實現單例模式
let Singleton = (function() {
  let instaced;
  function init() {
    console.log('init instance');
    //這裡定義單例程式碼
    return {
      publicMethord: function() {
        console.log("welcome to singleton");
      },
      publicProperty: "test"
    };
  }
  return {
    getInstance: function() {
      if (!instaced) {
        console.log('instance does not exit');
        //確保只有一個例項
        instaced = init(); //使用init方法,是使publicMethod和publicProperty只在要使用的時候才初始化;
      } else {
        console.log('instance already created');
      }
      return instaced;
    }
  };
})();
/*呼叫公有的方法來獲取例項:*/
// 第一次呼叫
// 單例物件是在呼叫getInstance的時候才真正被建立
Singleton.getInstance()
// 第二次呼叫
Singleton.getInstance().publicMethord();
複製程式碼

效果如下圖所示

這篇文章只簡單通俗的講單例模式

作用和注意事項

bb了一頓,還不知道在實際業務中有什麼用 下面我們來看看再實際業務中得作用以及注意事項和一個業務中常見得例項

模式作用

  • 模組間通訊
  • 系統中某個類的物件只能存在一個
  • 私有屬性和方法的保護
  • 明確模組職責

注意事項

  • 注意this的使用
  • 閉包容易造成記憶體洩露,不需要的要趕快清除
  • 繼承時new的成本需要注意
  • 不恰當的使用和增加耦合度。

業務例項

我們來實現一個對話方塊,無論點選多少次,始終只建立一個例項物件。

  • 第一步建立我們得對話方塊建構函式
/**
 * 構造器
 * @param {*string} id 
 * @param {*string} html 
 */
let Modal = function(id, html) {
  this.html = html
  this.id = id
  this.domInstance = null  // 私有dom例項
  this.open = false // 是否開啟
}
複製程式碼

這裡我們宣告瞭一個 Modal作為彈框的建構函式並且再其內部定義了公有屬性 html、id 和 open。html 用來定義對話方塊內部的內容,id 用來給彈框定義 id 名稱,open 用於判斷彈框是否開啟。

  • 第二步宣告建立私有類的各種方法
// create 方法
Modal.prototype.create = function() {
  if (!this.open) {
    console.log('create dom instance')
    // 構建DOM
    const modal = document.createElement("div");
    modal.innerHTML = this.html;
    modal.id = this.id;
    document.body.appendChild(modal);

    setTimeout(function() {
      modal.classList.add("show");
    }, 0);

    this.open = true;
  }
}
Modal.prototype.hide = function() {
  if (this.open) {
    this.domInstance.classList.add("hide");
    this.open = false
  }
}
// 注意這個delete函式 並不是銷燬我們產生的Modal例項,而是銷燬頁面的DOM例項
Modal.prototype.delete = function() {
  // 刪除延遲
  let time = this.open ? 0 : 200
  if (this.domInstance) {
    setTimeout(() => {
      document.body.removeChild(this.domInstance)
      this.domInstance = null
    }, time)
  }
}
複製程式碼

在 Modal 的原型鏈上定義了 create 方法,方法內部我們建立並向 DOM 中插入彈框。定義了 create 方法後我們這裡定義隱藏彈框的方法,在其內部給彈框物件新增 hide 類,最後在定義delete方法移除頁面上彈框例項。

  • 第三步建立例項方法
// 建立一個Modal例項
let createInstance = (function() {
  /*
  使用閉包來儲存當前的例項,這個是單例模式中至關重要的一個部分。
  */
  let instance
  return () => {
    console.log(instance)
    // debugger 用
    if (instance) {
      console.log('已經存在例項了')
    }
    // 判斷當前時候還存在以一個例項,如果存在就返回這個例項,不存在的話就生成一個
    return instance || (instance = new Modal("modal", "這是一個單例的模態框"));
  }
})()
複製程式碼

我們使用一個閉包來完成對當前例項的儲存

  • 第四步封裝操作物件
let operate = {
  setModal: null,
  // open
  open() {
    this.setModal = createInstance();
    this.setModal.create();
  },
  // hide
  hide () {
    this.setModal ? this.setModal.hide() : "";
  },
  // 這個操作 delete的是頁面中的dom, 而並不是Modal例項
  delete() {
    this.setModal ? this.setModal.delete() : "";
  }
}
複製程式碼

這裡我們將按鈕操作放在 operate 物件裡,使得開啟和關閉操作可以通過this獲取例項setModal。在實際的使用中我們可能只會丟擲這個操作物件,然而真正的處理方法呼叫者並不用知情。

從上面的例子我們可以看出來,例項建立new Modal("modal", "這是一個單例的模態框")之後, 之後的操作僅是對產生的例項物件的行為。

  • 繫結事件進行驗證
// test
document.getElementById('open').onclick = function () {
  operate.open();
}
document.getElementById('hide').onclick = function () {
  operate.hide()
}
document.getElementById('delete').onclick = function () {
  operate.delete()
}
複製程式碼

在這個例子中注意我們的單例指得並不是頁面上的那個模態框, 而是操作我們模態框的Modal例項

總結

單例模式是比較簡單常用的一種模式,而且應用也是非常的廣泛。按照我個人的理解單例會把各種強耦合的模組組合成一個類,這個類僅僅會提供出一個例項供呼叫者使用。例如很多物件我們只希望建立一次。比如我們需要顯示給使用者一個資訊頁面,內容不變但是使用者會多次點選。這個頁面無論使用者點選多少次,我們只需要建立一次。那這種情況下就非常的適合使用單例模式。上面得這個示例我相信大部分前端工程師10分鐘就能寫的很完美。為了演示單例模式的應用我才這麼寫,在實際得業務中,我們大家一般不會去那麼寫。

設計模式在我看來是一把雙刃劍,用的好的話程式碼結構清晰,可以實現理想的高內聚低耦合。用的不好的話程式碼可能會變得一塌糊塗,可讀性和可維護性基本為0。至於到底要不要用,那就見仁見智了。

設計模式程式碼地址

文章程式碼地址

原文地址 如果覺得有用得話給個⭐吧

相關文章