在設計程式時,我們有時會希望某些程式資源是可以被重複使用,並且在不同的程式中被引用時,變數內容可以是一致的。例如說,我希望我的程式可以共用同一個 DB 物件、同一個計時排程…諸如此類的需求,這時候 Singleton 單例設計模式就派上用場了。
在 JavaScript 的世界中,要實現 Singleton 常常會因為同步/非同步的特性導致同時建立了多個不同的實例(instance),造成每個程式所對應到的參照物件是獨立的個體,也就造成了存取資料的不同及錯誤的預期行為。而就算是在 Node 引用同一個 JavaScript 檔案,也會出現相同的情況。
那麼,在 JavaScript 中要如何確實地達成 Singleton 設計模式呢?
其實不難,也是利用 JavaScript 的特性,IIFEs(Immediately Invoked Function Expressions, IIFEs) 以及單執行緒。
好在 JavaScript 是單執行緒的執行環境,因此我們不需要特別設計特殊的鎖來避免多個實例被產生出來,我們要做的只有一件事情:在程式被引用時建立實例,並在每次被請求時將已存在的實例回傳。
foo.js
let instance;
class Foo {
  constructor() {
    this.name = 'Foo';
    this.bar = 1;
  }
  callMe() {
    console.log(`Hi! I'm ${this.name}`);
  }
}
module.exports = (() => {
  if (instance) {
    return instance;
  }
  instance = new Foo();
  return instance;
})();
 test-1.js
const foo = require('./foo.js');
module.exports = (val = 0) => {
  foo.val += val;
  foo.callMe();
};
 test-2.js
const foo = require('./foo.js');
module.exports = (val = 0) => {
  foo.val += val;
  foo.callMe();
};
 test.js
const foo = require('./foo.js');
const test1 = require('./test-1.js');
const test2 = require('./test-2.js');
// 先呼叫一次初始版本
foo.callMe();  // => Hi! I'm Foo, val: 1
// 呼叫其他程式
test1(10);  // => Hi! I'm Foo, val: 11
test2(20);  // => Hi! I'm Foo, val: 31
// 改個名字
foo.name = 'no one';
// 再來呼叫一次
foo.callMe();  // => Hi! I'm no one, val: 31
// 呼叫其他人看看
test1(5); // => Hi! I'm no one, val: 36
test2();  // => Hi! I'm no one, val: 36 這個範例完整的呈現了單例模式效果,相信眼尖的你一定有發現數值是一致的,沒有錯啦!這就是我們要的東西,全世界都操作同一個物件,再也不分你我啦!
我想,到這邊你對於 Singleton 單例設計模式應該會有點概念了,希望這可以讓你在設計程式的時候可以不再被明明是相同的物件卻一直拿到不同結果的問題所困擾。
參考資料
View Comments
foo.js 那邊的bar應該要改成val?