Aug 14, 2005

JavaScriptのデザインパターン - Singleton

JavaScriptじゃねえと書けねえよ、ってやり方でデザインパターンを実装してみるコーナー。とはいってもデザインパターンとか良くわからないので適当に覚えながら作る。

間違ってる箇所あったらつっこんでくれるとありがたいです。

わかりやすい文章を書く能力が欠如してるのでデザインパターンって何だとかそういうのはこっち参照。
http://d.hatena.ne.jp/naoya/20050813/1123924312


JavaScriptのコンストラクタはPerl同様自在に定義できます。returnでobjectを返してやれば、newの結果としてそいつを使います。

普通にシングルトンなクラスを実装するにはこんな感じだと思います。

function Singleton(){
    var self = arguments.callee;
    if(self.instance == null){
        this.initialize.apply(this,arguments);
        self.instance = this;
    }
    return self.instance;
}
Singleton.prototype = {
    initialize : function(o){
        this.foo = o;
    }
};
var a = new Singleton("aaa");
var b = new Singleton();
var c = Singleton.instance;
alert(a.foo); // aaa
alert(a==b); // true
alert(b==c); // true 全部おんなじ



arguments.calleeは実行中の関数を参照します。コンストラクタ関数のinstanceプロパティに生成したオブジェクトをキャッシュしておいて、同じオブジェクトを使いまわすようにします。これで、いくらnewしても同じオブジェクトが出来上がるようになります。

newを禁止したい?できないこともない。

せっかくなのでnewを禁止して、getInstanceでオブジェクトを取得するように関数を変形するFunction.prototype拡張を作ってみよう。

Function.prototype.toSingleton = function(){
    var ctor = this;
    var Dummy = function(){};
    Dummy.prototype = ctor.prototype;
    var new_func = function(){
        if(new_func.__allow_create__ == null){
            throw "this is SingletonClass. use getInstance()";
            return null;
        }
        return ctor.apply(this,arguments)
    };
    new_func.getInstance = function(){
        if(new_func.instance == null){
            var tmp = new Dummy;
            var res = ctor.apply(tmp,arguments);
            new_func.instance = (typeof res == "undefined")?tmp:res;
        }
        return new_func.instance;
    };
    new_func.prototype = ctor.prototype;
    return new_func;
};

// やっぱシングルトンじゃなくする。
Function.prototype.removeSingleton = function(){
    this.__allow_create__ = true;
}



getInstanceはnewを使わないのでthisキーワードが使えない。thisを使ったらnew_funcへの参照になってしまった。なので、空のオブジェクトを生成して、そいつをthisとしてコンストラクタ関数に引渡し、返値もしくはthisをインスタンスにセット、というnewの挙動を再現するようにした。これで大体問題ないはず。

空のオブジェクトを作成するのにDummyを使うのは、元のコンストラクタ関数のprototypeを継承するため。これをやらないとprototype.jsのthis.initializeみたいな処理が呼べない。

__allow_create__っていうフラグが立っていない場合は関数としての呼び出しは許可されないようにする。newでインスタンスを生成しようとしたらthrowでgetInstanceを使えとエラーメッセージを投げてやる。

シングルトンパターンはDBのコネクションを一つに絞ったりするのに使うらしいです。
まず、複数のインスタンスが生成されることを全く考慮しないで設計したConnectionクラスを作って、Connectionをシングルトンに変換させたConnectを実際に使用する、といった具合になります。

全然具体的じゃないサンプルですが一応。

// ベースになるクラス定義
function Connection(option){
    this.name = option;
}
// シングルトンに変形
Connect = Connection.toSingleton();
var db = Connect.getInstance("myname");
var db2 = Connect.getInstance("hogehoge"); // 引数無意味
alert(db==db2); // true

// 短く書くとこう
var Connect = function(option){
    this.name = option;
}.toSingleton();

// 上書きしても問題ない
Connection = Connection.toSingleton();
// やっぱnewで使う
Connection.removeSingleton();
var hoge = new Connection;

// prototype.js使うならこう。
var MyClass = Class.create();
MyClass.prototype = {
    initialize : function(option){
        this.name = option
    }
}
MyClass = MyClass.toSingleton();
var huga = MyClass.getInstance();



こんな具合で既存のコンストラクタ関数を何でもシングルトン化できる。

Class.create().toSingleton();と書きたいところだけど、そうするとMyClass.prototypeがシングルトン化された関数に対して関連付けられてしまうので上手くいかない。

いったんインスタンスを作ったら、getInstanceに引数を渡しても無視するようにした。なんかキーとか指定できるようにしたほうがいいのかな。それじゃシングルトンじゃないか。よく知らん。

デザパタとかオブジェクト指向のなんたるかとかは良くわかってないが、JavaScriptに関してはけっこう真面目に勉強しなおしたのでバッチリだ。今まで書いたコード全部書き直したいが単純に時間が足りない。
Posted at 12:06 | WriteBacks (2) | Edit
Edit this entry...

wikieditish message: Ready to edit this entry.
















A quick preview will be rendered here when you click "Preview" button.