あどけない話

Internet technologies

JavaScript で Singleton

Singleton は、インスタンスが1つしか作成されないようにするデザインパターンです。

Java では?

Javaインスタンスを作るには new するしかありません。Singleton では、1 回のみ new を許すように実装する必要があります。そこで、コンストラクタを private にし、getInstance という関数でインスタンスを取得するのが一般的です。

public class Singleton {
    private static int _state;
    private static Singleton singleton = new Singleton();
    private Singleton() {
    }
    public static Singleton getInstance() {
        retrun singleton;
    }
    public static int setState(state) {
        _state = state;
    }
}

JavaScript では?

JavaScript には、private のようなアクセス制御がありません。どうしたらいいでしょうか?

JavaScript には、アクセス制御がない代わりに、オブジェクト・リテラルがあります。ですから、単にオブジェクト・リテラルを書けばいいのです。

var Singleton = {
    setState: function(state) {
        this._state = state;
     },
    getState: function() {
        return this._state;
    }
};
(function() {
    var singleton = Singleton;
    singleton.setState('run');
})();

Singleton 病

Singleton、クラス変数、そしてクラス関数は、本質的にグローバル変数/関数です。たとえ、その名前がグローバルの名前空間を汚していなくても、実体はプログラムの実行時から終了時まで存在し、どこからでもアクセスできるからです。

オブジェクト指向には以下のような鉄則があります。

  • クラス内の凝集度は高く
  • クラス間の結合度は低く

複数のクラスで同じグローバル変数/関数を利用すると、それらのクラス間の結合度は高くなってしまいます。

Singleton を介して 2 つのクラスが結びついているとしましょう。Singleton のクラス名を変更すると、これらのクラスの中でその Singleton を参照している部分を書き換えなくてはいけません。この作業自体はたいしたことはありませんが、コードは変更しなくてもすむように作っておくべきです。

また、Singleton を使うと状態(コンテキスト)が1つしかないのも問題です。こちらの方がより本質的な問題だと思います。他のクラスに迷惑をかけずに Singleton の状態を書き換えるには、状態を一旦退避し、また元に戻すという作業が必要になることがあります。これは、バグの温床になります。

複数の状態が必要であれば、素直にマルチ・インスタンスにして、引数として渡すべきです。引数が複数の関数を深く渡って行くのが嫌だから Singleton を使っている場合、設計自体を見直すべきかもしれません。

Singleton を利用する場合は、あらかじめよく考えてからにしましょう。1つのクラスからしか利用されない State パターンなどを作る際には、とても有効です。

名前を隠す

Singleton のクラス名をハードコーディングしていると、違う Singleton を使いたくなった場合に、参照している部分をすべて書き換えないといけません。

Singleton のクラス名を設定で変更できるようにしておけば、変更する箇所はその設定のみとなります。たとえば、以下のようにします。

var Config = {
    SingletonName: 'Singleton'
};
(function() {
    var singleton = window[Config.SingletonName];
})();

違う Singleton を使いたくなったら、Config の SingletonName を書き換えればいいのです。

この手法は、マルチ・インスタンスのクラスでも利用できます。

初期化関数

僕は、Singleton を定義する場合に、prototype.js に習って initialize という関数を用意することにしています。

var Config = {
    SingletonName: 'Singleton'
};
var Singleton = {
    initialize: function(state) {
        this._state = state;
	return this;   
    },
    getState: function() {
        return this._state;
    }
};
(function() {
    var singleton = window[Config.SingletonName].initialize('run');
})();

こうしておくと、マルチ・インスタンスに変更するのは簡単です。(initialize の return this は、悪影響を及ぼしません。)

var Config = {
    SingletonName: 'Singleton'
};
var Singleton = Class.create({
    initialize: function(state) {
        this._state = state;
	return this;   
    },
    getState: function() {
        return this._state;
    }
});
(function() {
    var singleton = new window[Config.SingletonName]('run');
})();

ただし、複数の箇所から引数を指定して initialize を実行されると、状態がおかしくなるかもしれません。これについては、意見を下さい!