【JavaScript】デザインパターンを知ってみる。シングルトン編

【JavaScript】デザインパターンを知ってみる。シングルトン編

桜を見ながら飲み食いしたいminamiです。
前回に引き続きJavaScriptで書くデザインパターンです。今回はシングルトンパターンについて調べてみました。

なぜ使うの?

シングルトン(Singleton)パターンは、以下のような特徴があります。

  • あるクラスのインスタンスを一つだけにする
  • つまり同じクラスを使って新しいオブジェクトを再度作成すると、最初に作ったオブジェクトの参照になる。
  • 作られたオブジェクトへのグローバルなアクセス方法を提供する

機能の重複する新規のインスタンスを作ることなく、一つのオブジェクトを使いまわすことができるのでリソースを無駄にしません。また、インスタンスを一つだけにすることでグローバルな関数や変数を無駄に増やすことがなくなるので競合の危険をなくします。このあたりは前回のモジュールパターンの考え方と同様です。

簡単な書き方

var Single = {
  myName: 'Singleton'
};

JavaScriptには厳密にはクラスという概念はないので、新しくオブジェクトを作ればそれはシングルトンになります。
例えば以下のように全く同じ要素を持つオブジェクトでも、同じオブジェクトではありません。

var Single = {
  myName: 'Singleton',
  age: 35,
  method: function() {
    console.log('hoge');
  },
  status: 'fine'
};

var Double = {
  myName: 'Singleton',
  age: 35,
  method: function() {
    console.log('hoge');
  },
  status: 'fine'
};

// 以下の二つは違うオブジェクト
console.log(Single === Double); // false

newを使ったシングルトン

JavaScriptでは、コンストラクタ関数を作って、new することで新しいオブジェクトを作ることができます。

function Singleton () {
  this.myName = 'Singleton'; 
}

var single = new Singleton(); // new を使ってSingleton オブジェクトを作成する
console.log(single.myName); // Singleton

この場合、コンストラクタのプロパティに自身を格納して、既にオブジェクトがあるかどうかを判定する方法でシングルトンが作れます。

function Singleton () {
  // すでにSingleton.instance が存在する場合にはSingleton.instance を返す
  if(typeof Singleton.instance === 'object') {
    return Singleton.instance;
  }
  
  this.myName = 'Singleton';
  
  // Singleton.instance は自身を参照する。
  Singleton.instance = this;
  
  return this;
}

var obj1 = new Singleton(); // new を使ってSingletonオブジェクトを作成する
var obj2 = new Singleton(); // new を使ってSingletonオブジェクトを作成する
console.log(obj1 === obj2); // true 2つは同じオブジェクト

ただし、このSingleton.instance はpublicな値なので、明示的に上書きされると破綻してしまいます。

function Singleton () {
  if(typeof Singleton.instance === 'object') {
    return Singleton.instance;
  }
  
  this.myName = 'Singleton';
  
  Singleton.instance = this;
  
  return this;
}

var obj1 = new Singleton(); // new を使ってSingletonオブジェクトを作成する
Singleton.instance = "hoge" // 新しいオブジェクトで上書きできてしまう
var obj2 = new Singleton(); // new を使ってSingletonオブジェクトを作成する
console.log(obj1 === obj2); // false 2つは違うオブジェクト

下記のようにコンストラクタそのものを書き変えてしまうことによって上書きされる問題を回避することもできます。

function Singleton () {
  var instance = this; // 外からはアクセスできない
  if(typeof Singleton.instance === 'object') {
    return Singleton.instance;
  }

  this.myName = 'Singleton';

  // 一度呼ばれたらコンストラクタそのものを上書き
  Singleton = function() {
    return instance;
  };

  return this;
}

var obj1 = new Singleton(); // new を使ってSingletonオブジェクトを作成する
Singleton.instance = "hoge" // 新しいオブジェクトで上書きできない
var obj2 = new Singleton(); // new を使ってSingletonオブジェクトを作成する
console.log(obj1 === obj2); // true 2つは同じオブジェクト

この方法だと最初のオブジェクトを作った後にprototypeに後から追加されたプロパティに、その後生成されたオブジェクトはアクセスできなくなります。コンストラクタを書き変えてしまうので当然と言えば当然ですね。
そのあたりを修正したのが以下のコードになります。

参考:

function Singleton () {
  var instance;
  // 一度呼ばれたらコンストラクタそのものを上書き
  Singleton = function() {
    return instance;
  };
  // プロトタイプを引き継ぐ
  Singleton.prototype = this;
  // インスタンスをキャッシュ
  instance = new Singleton();
  // コンストラクタを再設定
  instance.constructor = Singleton;

  // ここから機能
  this.myName = 'Singleton';
  this.method = function() {
    console.log('hogehogehoge');
  }

  // insrtanceを返す
  return instance;
}

var obj1 = new Singleton(); // new を使ってSingletonオブジェクトを作成する
Singleton.prototype.hoge = "hoge" // 新しくプロパティを追加
var obj2 = new Singleton(); // new を使ってSingletonオブジェクトを作成する
console.log(obj2.hoge); // hoge 新しく追加したプロパティを参照できた

即時関数を使ったシングルトン

前回のモジュール・パターンでも使った即時関数を使った書き方もできます。現実的にはこれが一番使用に耐えそうですね。

var Singleton = (function() {
  // ここは外側からアクセスできない機能
  
  var instance;
  
  function init() {
    // オブジェクトを返す
    return {
      prop: 1,
      sayHoge: function() {
        console.log('hoge');
      }
    }
  }

  return {
    // ここから公開された機能
    
    getInstance: function() {
      if(!instance) {
        instance = init(); // instanceがなければ init()
      }
      return instance; // instanceを返す
    }
  }
})();

var obj1 = Singleton.getInstance();
var obj2 = Singleton.getInstance();

// obj1とobj2は同じオブジェクト
console.log(obj1 === obj2); // true

// 同一オブジェクトなのでobj2の値を変えるとobj1も変わる
console.log(obj1.prop); // 1;
console.log(obj2.prop); // 1;
obj2.prop = 3;
console.log(obj1.prop); // 3;
console.log(obj2.prop); // 3;

まとめ

シングルトンのいろいろな実装について調べてみました。

厳密な意味でのシングルトンをJavaScriptで作るには難があるようですが、JavaScriptでどう実装すればよいか?というアイデアの部分はいろいろと勉強になりました!

参考:

  • このエントリーをはてなブックマークに追加

この記事を読んだ人にオススメ