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 を実行されると、状態がおかしくなるかもしれません。これについては、意見を下さい!