MUKI AI Summary
最近在學習 JavaScript 設計模式時,發現自己常用 Singleton Pattern,但未深入理解。設計模式是可重複使用的解決方案,像數學公式一樣,能提高程式碼的可讀性與開發效率。設計模式分為建立型、結構型與行為型。
Singleton Pattern 屬於建立型設計模式,確保類別只有一個實例,避免資源浪費,適合用於全域狀態與共享資料管理。然而,過度使用可能導致狀態難以管理與測試困難。選擇是否使用 Singleton 需視具體需求而定。...
緣起
最近在看 JavaScript 設計模式學習手冊 第二版,才發現我在寫 JavaScript 時,一直都有用到 Singleton Pattern,但我卻不知道他叫做 Singleton Pattern,也沒有深入理解過他的特性與優缺點。
所以這次要幫自己補充知識點,分享我理解的 Singleton Pattern,也許還只是皮毛,也許還有錯誤與不足,都歡迎大家指正,感謝 🙏
設計模式的基本概念
免不俗,先講一下什麼是設計模式。
簡單來說,就是一種可以重複使用的解決方案,我個人喜歡拿數學公式做比喻:「某種類型的問題,套上特定的公式,就能得到正確的答案」。
而設計模式就是那些數學公式,我們在不同情境下碰到的問題,可以使用對應的設計模式來解決。雖然我們也能自己想出解法,但使用前人結構化的設計模式,能幫助我們提高程式碼的可讀性以及可維護性;也能讓我們愉快的重用,並提高開發效率。
設計模式有哪些
根據「JavaScript 設計模式學習手冊 第二版」一書的內容,作者 Addy 將設計模式分為三類:
- 建立型設計模式 creational design pattern
- 結構型設計模式 structural design pattern
- 行為型設計模式 behaviroal design pattern
我發現一個可愛的寶藏網站:Refactoring Guru,擁有圖文並茂的版面,分享這三種設計模式的類別。
建立型設計模式
◿ 來源:https://refactoringguru.cn/design-patterns/creational-patterns ◺
結構型設計模式
◿ 來源:https://refactoringguru.cn/design-patterns/structural-patterns ◺
行為型設計模式
◿ 來源:https://refactoringguru.cn/design-patterns/behavioral-patterns ◺
什麼是 Singleton 設計模式
今天要分享的 Singleton 設計模式,是分類在「建立型設計模式」裡,所以我們是在處理建立物件機制時,會用到 Singleton 設計模式。
以往我們在建立類別時,可以有很多個實例,而且每個實例都是獨立的。
但,Singleton 設計模式是讓一個類別只會產生一個實例。即使你試圖建立該類別的多個實例,你會發現,儘管看似是獨立的實例,但實際上,他們都指向同一個實例。這就是 Singleton 設計模式的魅力所在。
接下來,我會先寫一個基本的 Class,再寫一個有 Singleton Pattern 的 Class,再來比較他們之間的差別。
基本的 Class 範例
▼ 在 JavaScript 中,我們可以使用 class
關鍵字來定義一個類別,並使用 constructor
來定義該類別的建構子。
// 定義一個 Human 類別 class Human { // 定義建構子 constructor(name, sex) { this.name = name; this.sex = sex; } // 定義一個方法 drink(val) { console.log(`${this.name} 喜歡喝${val}`) } } // 建立 Human 實例 let muki = new Human("MUKI", "female") let dca = new Human("DCA", "male") // 使用實例的方法 muki.drink('咀嚼系飲料') // 輸出:MUKI 喜歡喝咀嚼系飲料 dca.drink('果汁') // 輸出:DCA 喜歡喝果汁
▼ 我們建立了兩個實例,分別為 muki
與 dca
,可以透過運算子來判斷這兩個實例是否相同
console.log(muki === dca) // false
回傳 false
的結果可知,我建立了兩個獨立的實例,他們完全不同。
改寫為 Singleton 設計模式
Singleton 設計模式的精髓,就是一個類別只能有一個實例。我們在建立類別時,會先判斷這個類別的實例存在與否?如果不存在,才能建立一個新的實例;如果存在,就回傳對物件的參照。
▼ 將剛剛的 Human
Class 改寫為 Singleton 設計模式,此外將 Human
設為私有,避免其他人繞過 HumanSingleton
直接建立 Human
// 使用函數表達式(IIFE)來建立一個私有的 Human 類別 let HumanSingleton = (function () { // 私有的 Human 類別 class Human { constructor(name, sex) { this.name = name this.sex = sex } drink(val) { console.log(`${this.name} 喜歡喝${val}`) } } let instance return { // 加入了 Singleton 設計模式 // 建立類別時,會先判斷這個類別的實例存在與否?如果不存在,才能建立一個新的實例;如果存在,就回傳對物件的參照。 getInstance: function (name, sex) { if (!instance) { instance = new Human(name, sex) } return instance } } })() let muki = HumanSingleton.getInstance("MUKI", "female") let dca = HumanSingleton.getInstance("DCA", "male") // 由於 HumanSingleton 是一個 Singleton,所以 muki 和 dca 實際上是同一個實例。 // 因此調用 dca.drink('果汁') 時,輸出的名字仍然是 "MUKI"。 muki.drink('咀嚼系飲料') // 輸出:MUKI 喜歡喝咀嚼系飲料 dca.drink('果汁') // 輸出:MUKI 喜歡喝果汁
▼ 一樣來判斷這兩個實例是否相同,回傳 true
可知,他們是一樣的實例
console.log(muki === dca) // true
跟前面的範例比對結果,會發現有兩個地方不同:
- 即使我建立了
dca
實例,但他其實是muki
實例,所以輸入dca.drink('果汁')
時,會顯示的是MUKI 喜歡喝果汁
muki
與dca
實例回傳true
,表示他們是同一個實例
會產生不同的結果,最大的原因是 21 ~ 24 行的程式碼加入了 Singleton 設計模式:「先判斷實例存在與否」這段程式碼,導致後面的結果不同。此時真正實現了一個 Class 只能有一個實例的設計模式。
延伸應用:JavasScript 的全域實例
在 ES2015+ 後,我們可以用 Singleton 設計模式建立 JavaScript Class 的全域實例,並能透過 export
/ import
module 的方式來設置並取得資料,也就是說在 Vue, React 這種現代框架上都能實作!
以下是一個簡易的 Vue.js 範例:
▼ 在 js 模組檔案,設定 set
與 get
的 function
let a = 0 export const setA = (val: number) => { a = val } export const getA = () => { return a }
▼ 假設我有兩個 Vue 檔案,分別為:home.vue 以及 setting.vue,都載入了 singleton.ts 模組檔
<script> import { setA } from '@/models/singleton.ts' setA(1234) </script>
<script> import { getA } from '@/models/singleton.ts' console.log('getA', getA()) </script>
此時,會有兩種結果:
- 如果我們先瀏覽 setting.vue,那
getA()
的結果就是他初始化的數值0
- 如果我們先瀏覽 home.vue,再瀏覽 setting.vue,
geA()
的結果,就是在 home.vue 呼叫setA(1234)
而修改過的數值1234
由此可知,透過 import
匯入的模組檔案,就是 Singleton 設計模式的一種實現方式,因為它們在整個應用程式中只會被建立一次,並且可以在多個地方被重複使用。這種特性,有效的減少了記憶體的浪費,也使得模組檔案適合用於儲存與管理全域的狀態,或共享資料 ... 等等。
Singleton 使用情境
我們在什麼情況下,會「刻意」選擇 Singleton 設計模式來設計我們的程式碼呢?
稍微翻了書跟查了資料,大部分還是在講當有「全域狀態」以及「共享資料」的需求時,可以考慮使用 Singleton 來規劃你的程式碼:
- 全域狀態:應用程式需要管理全域狀態時,可以做一個
set()
和get()
來處理,就像我前面提到的範例。 - 資源共享:可以用 Sinleton 管理共享資源,例如連結資料庫,確保所有的資料庫操作都是用同一個資料庫連結,以避免資源浪費。
但大家也不提倡過度使用 Singleton 設計模式,因為不是所有情況都適合使用。例,如果應用程式需要大量的狀態管理,並且需要追蹤狀態變化時,使用狀態管理的 Library 可能是更好的選擇。
Singleton 的優點與缺點
我覺得 Singleton 有點像雙面刃,他的優點正好也是他的缺點。他比較適合處理簡單不複雜的情境,如果情境複雜化,那當初的優點就會全部變成缺點。
- 例如,他可以管理全域狀態,但過多的使用,會使得全域狀態很難管理與追蹤。
- 例如,他可以確保我們共用一個實例,但也導致非常難做測試。
Singleton 是一種強大而靈活的設計模式,它在許多情況下都非常有用。然而,就像所有的工具一樣,我們需要根據具體的需求和情況來選擇是否使用它。
結語
文章跟大家分享了我自己對 Singleton 的看法和理解,也許還只是皮毛,也許還有錯誤與不足,都歡迎大家指正,感謝 🙏