# Singleton Pattern (單體模式) ###### tags: `Desgin Pattern` ## Introduce - Share a single global instance throughout our application - Ensure a class only has one instance, and provide a global point of access to it. > Singletons 是被實例化一次的類別,這個類別(single instance)能訪問整個 application OOP 分為兩種類型: - 基於「類別」(Class-based) 的物件導向 Ex: Java - 基於「原型」(Prototype-based) 的物件導向 Ex: Javascript 實例 (Instance) 和類別 (Class): - 類別 Class:用來描述擁有某種屬性、方法,是一種廣泛的概念。 - 實例 Instance:是一個實際建立的個體。 建構函式: 被使用 new 來「建立 / 實例化(Instantiation)」實例的函式,就稱為建構函式 > An object created by a constructor is an instance of that constructor. ```javascript= // 建構函式 function Car() {} // instance const toyota = new Car(); ``` new 這個運算子幫我們做: - 建立了新的記憶體空間與物件 - 將 this 指向新的物件 - 執行函式 - 回傳這個物件 回到正題 Singletons 幫我們有效的管理 global state,舉個例子: **未使用 Singletons Pattern** ```javascript= // 1.建立 Couter 類別 class Couter { let count = 0; // returns the value of the instance getInstance() { return this } // returns the current value of the count variable getCount() { // return this.count return count } // increments the value of counter by one increase() { return ++count } // decrements the value of counter by one decrease() { return --count } } const counter1 = new Counter(); const counter2 = new Counter(); console.log(counter1.getInstance() === counter2.getInstance()); // false ``` > counter1 與 counter2 是不同的 instance [Video Demo](https://res.cloudinary.com/ddxwdqwkr/video/upload/v1609056519/patterns.dev/jspat-52_zkwyk1.mp4) **使用 Singletons Pattern** 要確保 Counter Class 只能建立一個 instance 的方法就是 ```javascript= // 1.Creating a variable called instance let instance; let counter = 0; class Couter { // 2.Prevent new instantiations by checking if the instance variable already had a value if(instance) { throw new Error('You can only create one instance!') } // 3.We can set instance equal to a reference to the instance when a new instance is created. instance = this; getInstance() { return this } getCount() { return count } increase() { return ++count } decrease() { return --count } } // Object.freeze method makes sure that consuming code cannot modify the Singleton. Properties on the frozen instance cannot be added or modified, which reduces the risk of accidentally overwriting the values on the Singleton. const singletonCounter = Object.freeze(new Counter()); export default singletonCounter; ``` [Video Demo](https://res.cloudinary.com/ddxwdqwkr/video/upload/v1609056519/patterns.dev/jspat-56_wylvcf.mp4) ## Example [Counter Demo](https://codesandbox.io/s/singleton-counter-example-wycmzi) (Using a regular object) [Cat Demo](https://codepen.io/s110319022/pen/VwQZWyV?editors=1011) ## (Dis)advantages 優點: - 限制只能產生一個 instance 省下了大量的內存 (memory space) - 可以共用同一個 instance 缺點: - Singletons are actually considered an anti-pattern (can avoid in JavaScript) ## Testing 因為 Singletons 不能每次新增一個 instance,所以所有的 test case 都是修改同一個 global instance,也就是說測試順序很重要,每次測試的 instance,都是上一個測試完的結果,最後測試結束還需要重置整個 instance ```javascript= import Counter from "../src/counterTest"; test("incrementing 1 time should be 1", () => { Counter.increment(); expect(Counter.getCount()).toBe(1); }); test("incrementing 3 extra times should be 4", () => { Counter.increment(); Counter.increment(); Counter.increment(); expect(Counter.getCount()).toBe(4); }); test("decrementing 1 times should be 3", () => { Counter.decrement(); expect(Counter.getCount()).toBe(3); }); ``` [Demo Code](https://codesandbox.io/s/singleton-react-counter-dnlolw) ## Dependency hiding 若呼叫某個 module 時,這個 module 內有引用到 Singletons,就需要注意會有不小心修改到 global instance 的問題,如下: ```javascript= import Counter from "./counter"; export default class SuperCounter { constructor() { this.count = 0; } increment() { Counter.increment(); return (this.count += 100); } decrement() { Counter.decrement(); return (this.count -= 100); } } ``` [Demo Code](https://codesandbox.io/s/singleton-react-counter-dnlolw) ## Global behavior 說明 singleton 發展後,轉案會遇到的問題 - Global scope pollution can end up in accidentally overwriting the value of a global variable (bad design decision) - The new `let` and `const` keyword prevent developers from accidentally polluting the global scope(block-scoped) - The new `module` system in JavaScript makes creating globally accessible values easier without polluting the global scope (`export`, `import`) - global state(**mutable object**) can lead to unexpected behavior. ## State management in React - Redux、React Context 取代 singleton - 這些工具提供的 state 是 read-only 的,Singleton 的 state 是 mutable - component has sent an action through a dispatcher. - pure function reducers can update the state ## Reference - [Object.freeze](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) @ MDN - [Instance](https://developer.mozilla.org/en-US/docs/Glossary/Instance) @MDN - [實例 (Instance) 和類別 (Class) 到底是什麼?](https://medium.com/itsems-frontend/javascript-class-and-instance-410bd6281576) @Medium - [Singleton Pattern](https://www.patterns.dev/posts/singleton-pattern/) @patterns.dev - [JAVASCRIPT設計模式-- 單體(SINGLETON)模式](https://lucrelin.blogspot.com/2016/11/javascript_8.html) @關於程式的那些事