prototype.js でデザインパターン - Prototype

続きまして「コピーしてインスタンスを作る」Prototype パターンです。デザパタ本では6章にあたります。

var Main = Class.create();
Main.prototype = {
    initialize : function () {},
    main : function() {
        var manager = new Manager();
        var upen = new UnderlinePen('~');
        var mbox = new MessageBox('*');
        var sbox = new MessageBox('/');
        manager.register("strong message", upen);
        manager.register("warning box", mbox);
        manager.register("slash box", sbox);

        document.writeln('<pre>');

        var p1 = manager.create("strong message");
        p1.use("Hello, world.");
        var p2 = manager.create("warning box");
        p2.use("Hello, world.");
        var p3 = manager.create("slash box");
        p3.use("Hello, world.");

        document.writeln('</pre>');
    }
}

というクライアントがあって、実行結果は

"Hello, world."
 ~~~~~~~~~~~~~
*****************
* Hello, world. *
*****************

/////////////////
/ Hello, world. /
/////////////////

となることが期待されますと。

Manager インスタンスに文字列生成のための各種雛形インスタンスをぽこぽこ放り込んで、後から "strong message" とか "warning box" といった文字列リテラルをキーに、登録されたインスタンスのコピーを生成して使ってるところがポイントですね。manager.create() で取り出してるインスタンスはあくまで最初に登録したインスタンスのコピーなので、そいつをいじっても登録した雛形インスタンスには影響を及ぼしません。

さて、その Manager クラスです。

var Manager = Class.create();
Manager.prototype = {
    initialize : function() {
        this.showcase = new Object;
    },
    register : function (name, proto) {
        this.showcase[name] = proto;
    },
    create : function(name) {
        var p = this.showcase[name];
        return p.createClone();
    }
}

Manager にはインスタンスを登録します。その登録されたインスタンスを保持しておくデータ構造は文字列をキーにしたデータ構造...連想配列が良さそうです。と言うことでデザパタ本の Java な実装では HashMap を使っています。JavaScript では Object を連想配列として使うことができるので、それを利用しています。

この Manager クラスの肝は

create : function(name) {
    var p = this.showcase[name];
    return p.createClone();
}

ここですね。登録されてるインスタンス連想配列から取り出して返却するのですが、そのときにそのまま取り出して返却するんではなく、インスタンスに実装されてる createClone() を呼び出して返却すると。createClone() の中ではインスタンスのコピーが行われます。

で、その登録するインスタンス、の抽象クラスを用意します。

Abstract.Product = function() {};
Abstract.Product.prototype = {
    use : function() {},
    createClone : function() {
        var obj = new Object;
        for (var key in this) obj[key] = this[key];
        return obj;
    },
}

本では Product は interface になってますが、ここでは createClone メソッドをサブクラスごとに実装するのではなくスーパークラスでまとめられそうだったので、抽象クラスにしました。

createClone() は、自分自身のコピーインスタンスを返却するメソッドです。JavaScriptインスタンスをコピーする方法は組み込みで用意されているか調べたのですがぱっと見見つからなかったので自前で実装。ここでは for/in でプロパティをコピーしてます。この方法だとプリミティブ型はちゃんとコピーされますが、参照は辿りません。いわゆる shallow copy (浅いコピー)です。複雑なインスタンスに対して使うなら、深いコピーを実装しないとですね。

この Abstract.Product が雛形インスタンスのクラス、つまりは Prototype です。Concrete Prototype を作っていきましょう。Abstract.Product を継承して use() を埋めます。

var UnderlinePen = Class.create();
UnderlinePen.prototype = (new Abstract.Product).extend({
    initialize : function(ulchar) {
        this.ulchar = ulchar;
    },
    use : function(s) {
        document.writeln('"' + s + '"');
        document.write(' ');
        for (var i = 0; i < s.length; i++) {
            document.write(this.ulchar);
        }
        document.writeln('');
    }
});

var MessageBox = Class.create();
MessageBox.prototype = (new Abstract.Product).extend({
    initialize : function(decochar) {
        this.decochar = decochar;
    },
    use : function(s) {
        for (var i = 0; i < s.length + 4; i++) {
            document.write(this.decochar);
        }
        document.writeln('');
        document.writeln(this.decochar + ' ' + s + ' ' + this.decochar);
        for (var i = 0; i < s.length + 4; i++) {
            document.write(this.decochar);
        }
        document.writeln('');
    }
});

となりました。

Prototype パターンはいままで使ったことがないなあ。使いどころがありそうでない。というか GoF パターンで使ったことあるのって 23 のうちいくつあるんだろう。