# 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) @關於程式的那些事