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

次は「具体的な処理をサブクラスにまかせる」Template Method パターン。定番ですね。

var Main = Class.create();
Main.prototype = {
    initialize : function () {},
    main : function () {
        var d1 = new CharDisplay('H');
        var d2 = new StringDisplay('Hello, world.');
        var d3 = new StringDisplay('Oh! JavaScript');
        d1.display();
        d2.display();
        d3.display();
    }
}

みたいなクライアントがあります。出力結果として、

[[HHHHH]]

+-------------+
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
|Hello, world.|
+-------------+

+--------------+
|Oh! JavaScript|
|Oh! JavaScript|
|Oh! JavaScript|
|Oh! JavaScript|
|Oh! JavaScript|
+--------------+

というのを期待してますと。なんとなく処理の呼び出しパターンは決まっていて、その中身の実装だけが違うと言う匂いがしてきます。こんなときは Template Method。

Iterator と Adapter では interface に相当するものは省略してきましたが、今回は抽象クラスをちゃんと用意します。JavaScript での interface みたいなものは Perl とかと一緒で実質意味を持ちませんが、抽象クラスなら実装がスーパークラスにちょこっとあるので意味があります。

Abstract.Display = function() {};
Abstract.Display.prototype = {
    open : function() {},
    print : function() {},
    close : function() {},
    display : function() {
        this.open();
        for (var i = 0; i < 5; i++) {
            this.print();
        }
        this.close();
    }
}

さて、この抽象クラスの書き方ですが

Abstract.Display = function() {};
Abstract.Display.prototype = { ... };

となってていつものとはちょっと違う感じ。これは prototype.js の中で抽象クラスらしきものを作るのにこういうやり方でやってたのでそれをみようみまねしたもの。(Abstract.Foo とすると prototype.js の中の Abstract.Hoge とバッティングするかもしんないのでもしかしたらあんまよくないのかも)

open、print、close とか中身のないメソッドが用意されてますが、これは抽象メソッドな気分です。Perl だと sub foobar { die } とかしますけどね。function () { alert('abstract') } とかしてもいいのかもしれないけど。JavaScript のその辺の慣習はよくわからない。

さて、この抽象クラスを実装する Concrete クラスを作っていきます。CharDisplay と StringDisplay ですね。

CharDisplay = Class.create();
CharDisplay.prototype = (new Abstract.Display).extend({
    initialize : function(ch) {
        this.ch = ch;
    },
    open : function() {
        document.write("[[");
    },
    print : function() {
        document.write(this.ch);
    },
    close : function() {
        document.writeln("]]");
    }
});

StringDisplay = Class.create();
StringDisplay.prototype = (new Abstract.Display).extend({
    initialize : function(string) {
        this.string = string;
        this.width = this.string.length;
    },
    open : function () {
        document.writeln("<pre>");
        this.printLine();
    },
    print : function() {
        document.writeln("|" + this.string + "|");
    },
    close : function() {
        this.printLine();
        document.writeln("</pre>");
    },
    printLine : function() {
        document.write("+");
        for (var i = 0; i < this.width; i++) {
            document.write("-");
        }
        document.writeln("+");
    }
});

ひたすら document.write してるだけですので解説は不要でしょう。Java なひとだとかなり this を省略したい衝動に駆られそうですが、そうすると実行できません。

テンプレートを実装するクラスを増やしていけばいろんな出力が作れますよ、と。