設計模式: 從ES5 到 TypeScript ——單例模式

Back in 1994, a book was authored by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides that discusses 23 desgin patterns, titled Design Patterns: Elements of Resuable Object-Oriented Software. You may have heard of this book or the authors as Gang of Four (GoF).javascript

單例模式

單例模式(Singleton Pattern)是最簡單的設計模式之一。這種類型的設計模式屬於建立型 (Creational) 模式,它提供了一種建立對象的最佳方式。html

這種模式涉及到一個單一的類,該類負責建立本身的對象,同時確保只有單個對象被建立。這個類提供了一種訪問其惟一的對象的方式,能夠直接訪問,不須要實例化該類的對象。java

注意:git

  1. 單例類只能有一個實例。
  2. 單例類必須本身建立本身的惟一實例。
  3. 單例類必須給全部其餘對象提供這一實例。

單例模式是一種經常使用的模式,有一些對象咱們每每只須要一個,好比線程池、全局緩存、瀏覽器中的 window 對象等。在 JavaScript 開發中,單例模式的用途一樣很是普遍。試想一下,當咱們單擊登陸按鈕的時候,頁面中會出現一個登陸浮窗,而這個登陸浮窗是惟一的,不管單擊多少次登陸按鈕,這個浮窗都只會被建立一次,那麼這個登陸浮窗就適合用單例模式來建立。es6

關鍵點

意圖:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。github

主要解決:一個全局使用的類頻繁地建立與銷燬。typescript

什麼時候使用:當您想控制實例數目,節省系統資源的時候。設計模式

如何解決:判斷系統是否已經有這個單例,若是有則返回,若是沒有則建立。瀏覽器

關鍵代碼:構造函數是私有的。緩存

UML

咱們將建立一個 SingleObject 類。SingleObject 類有它的私有構造函數和自己的一個靜態實例。

SingleObject 類提供了一個靜態方法,供外界獲取它的靜態實例。SingletonPatternDemo,咱們的演示類使用 SingleObject 類來獲取 SingleObject 對象。

image

ES5

面向對象

要實現一個標準的單例模式並不複雜,無非是用一個變量來標誌當前是否已經爲某個類建立過對象,若是是,則在下一次獲取該類的實例時,直接返回以前建立的對象。代碼以下:

var Singleton = function(name) {
  this.name = name
}
Singleton.prototype.getName = function() {
  alert(this.name)
}
Singleton.getInstance = (function() {
  var instance = null
  return function(name) {
    if (!instance) {
      instance = new Singleton(name)
    }
    return instance
  }
})()

var a = Singleton.getInstance( 'sven1' ); 
var b = Singleton.getInstance( 'sven2' );
alert ( a === b ); // true

複製代碼

咱們經過 Singleton.getInstance 來獲取 Singleton 類的惟一對象,這種方式相對簡單,但有 一個問題,就是增長了這個類的「不透明性」,Singleton 類的使用者必須知道這是一個單例類, 跟以往經過 new XXX 的方式來獲取對象不一樣,這裏偏要使 Singleton.getInstance 來獲取對象。

上面單例模式的實現,更多的是接近傳統面嚮對象語言中的實現,單例對象從 「類」 中建立而來。在以類爲中心的語言中,這是很天然的作法。好比在 Java 中,若是須要某個對象,就必須先定義一個類,對象老是從類中建立而來的。

class-free

但 JavaScript 實際上是一門無類(class-free)語言,也正由於如此,生搬單例模式的概念並沒有意義。在 JavaScript 中建立對象的方法很是簡單,既然咱們只須要一個「惟一」的對象,爲什 麼要爲它先建立一個「類」 呢?這無異於穿棉衣洗澡,傳統的單例模式實如今 JavaScript 中並 不適用。

1.使用命名空間

適當地使用命名空間,並不會杜絕全局變量,但能夠減小全局變量的數量。 最簡單的方法依然是用對象字面量的方式:

var namespace1 = {
  a: function() {
    alert(1)
  },
  b: function() {
    alert(2)
  },
}
複製代碼

2.使用閉包封裝私有變量

這種方法把一些變量封裝在閉包的內部,只暴露一些接口跟外界通訊:

var namespace = {
  getSingleton: (function() {
    // BEGIN iife
    var singleton
    return function() {
      if (!singleton) {
        singleton = {
          amethod: function() {
            console.log('amethod')
          },
        }
      }
      return singleton
    }
  })(), // END iife
}
// Invoke: namespace.getSingleton().amethod()

複製代碼

ES6

ES6 裏有了模塊和類的概念,實現起來會變得不同:

const singleton = Symbol();
const singletonEnforcer = Symbol();

class SingletonEnforcer {
  constructor(enforcer) {
    if (enforcer !== singletonEnforcer) {
      throw new Error('Cannot construct singleton');
    }

    this._type = 'SingletonEnforcer';
  }

  static get instance() {
    if (!this[singleton]) {
      this[singleton] = new SingletonEnforcer(singletonEnforcer);
    }

    return this[singleton];
  }

  singletonMethod() {
    return 'singletonMethod';
  }

  static staticMethod() {
    return 'staticMethod';
  }

  get type() {
    return this._type;
  }

  set type(value) {
    this._type = value;
  }
}

export default SingletonEnforcer;
複製代碼

Typescript

TypeScript 中有了 private等概念,實現起來會更加有趣。

讓咱們想象一下,咱們想要一個跟蹤溫度的類。在這個系統中,咱們但願有一個入口,能夠改變溫度。這就是 Singleton類 的樣子:

class Singleton {
  private static instance: Singleton;
  private _temperature: number;
  private constructor() { }
  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
      Singleton.instance._temperature = 0;
    }
    return Singleton.instance;
  }
  get temperature(): number {
    return this._temperature;
  }
  set temperature(score) {
    this._temperature = score;
  }
  increaseTemperature(): number {
    return this._temperature += 1;
  }
  decreaseTemperature(): number {
    return this._temperature -= 1;
  }
}

const myInstance = Singleton.getInstance();
console.log(myInstance.temperature); // 0
複製代碼

上面的實現有一下幾點須要注意:

  • 構造函數 constructor 前面使用了 private 修飾:這意味着咱們將沒法使用 new 關鍵字實例化該類。
  • 咱們首先檢查咱們是否有一個類的實例 instance,若是沒有,咱們將建立並返回實例自己。

若是使用 new 關鍵字建立對象:

const myInstance = new Singleton(); // Constructor of class 'Singleton' is private and only accessible within the class declaration.
複製代碼

根據上面的例子,咱們也能夠訪問 temperature 屬性。如今讓咱們設置溫度值並將其增長/減小几回:

console.log(myInstance.temperature = 25); // 25
console.log(myInstance.increaseTemperature()); // 26
console.log(myInstance.increaseTemperature()); // 27
console.log(myInstance.decreaseTemperature()); // 26
複製代碼

小結

在 java 中,單例模式根據是否 lazy loading (懶漢模式/餓漢模式)以及是否線程安全,分爲不少種實現方式。而在 JS 中,咱們不用關注線程安全的問題,所以不管是代碼複雜度仍是實現難度都會低不少。

參考

相關文章
相關標籤/搜索