首先,先來看看一個簡單、特殊的創造物件的模式。
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one "single" instance. This is useful when exactly one object is needed to coordinate actions across the system.
Singleton (單例模式) 限制了一個類別只能建立唯一的實例。
這是什麼意思呢?如果我們先來看看一般的類別,譬如先前的 BaseballPlayer
,這裡我們稍微簡化一下,並且利用它產生兩個實例 playerA 和 playerB
class BaseballPlayer {
constructor() {}
}
const playerA = new BaseballPlayer()
const playerB = new BaseballPlayer()
很明顯的 playerA 和 playerB 是兩個不同的東西
console.log(playerA === playerB) // false
那麼,我們要如何讓一個類別只能建立一個唯一的實例,譬如
class BaseballPlayer {
// do something
}
const playerA = new BaseballPlayer()
const playerB = new BaseballPlayer()
然後讓 playerA 和 playerB 都會拿到同樣的實例
console.log(playerA === playerB) // true
在進入實作之前,先來看看這個模式想要解決什麼問題。
當使用者透過類別建立實例的時候,每個實例就是獨立的個體,並可以各自執行各自的行為。
但如果我們想要「集中控管」使用者使用這個工具的行為,與其讓使用者自己透過類別創造出各自新的工具,不如就想辦法讓使用者每次透過類別取得實例的時候,都取得「同一個工具」,也就會使用同一個工具。
對我們來說,我們只要控制這一個工具(實例),就等於控制了所有使用者手上的工具。
另一方面,如果有一個變數是需要讓所有實例共享的的話,那麼也可以透過這個方式,讓每一位使用者能夠藉由取得同一個實例來操作同一個變數。
在系統當中,有些功能是高度共享的,如果這時候採用單例模式,就會便於管理。譬如 logger 或是 cache,不管是誰呼叫了這些實例或功能,都被集中管理,以避免不同實例各自操作所可能產生的 concurrent 問題
要實作單例模式,有一些不同的方法,這裡使用「靜態類別」的方法來實作。
靜態類別的特性是,可以不用建立實例就可以取用其方法。所以這裡我們利用這個特性,不讓使用者建立實例 (e.g., new Gov()
),而是直接呼叫靜態方法 getInstance
來取得實例。
所以在下面的程式碼當中,如果使用者呼叫了 getInstance
但此時還沒有任何實例,那麼就在這個方法內部建立新的實例。但如果實例已經存在,那麼就直接回傳該實例。
class Gov {
private static instance: Gov
private constructor() { }
public static getInstance(): Gov {
if (!Gov.instance) {
Gov.instance = new Gov()
}
return Gov.instance
}
}
如此一來,就可以確保所有使用這個類別的使用者,都可以拿到同一個實例
const yourGov = Gov.getInstance()
const myGov = Gov.getInstance()
console.log(yourGov === myGov) // true
單例模式的優點是,藉由提供所有使用者同樣的實例,讓我們可以集中管理資料、操作行為。
但是缺點是,它違反了單一功能原則,因為他控制了
也因為集中管理,導致擴展性不佳。如果有任何的新需求進入,都必須更改這個類別當中的實作方式。另一方面,也可能會隱藏了它與其他類別的關係,因為對於使用者來說,只要直接取用實例就行,但如果要理解功能是如何實作,就必須進入這個類別的程式碼查看,無法透過相對外顯的繼承、參數傳遞等方式來更快理解。