Ruby によるデザインパターン 第4章の Strategy を JavaScript で写経

Rubyによるデザインパターン

Rubyによるデザインパターン


レポート出力のところとかはしょっています。


ストラテジオブジェクトのメソッド呼出時に、ストラテジが必要とするすべてを引数として渡す

var Report        = function () { this.initialize.apply(this, arguments) };
var htmlFormatter = function () {};
var xmlFormatter  = function () {};

Report.prototype = {

    initialize: function(formatter) {
        this.title       = 'タイトル';
        this.description = '説明';
        this.formatter   = formatter;
    },

    output_report: function() {
        this.formatter.output_report(this.title, this.description);
    }

}

htmlFormatter.prototype = {

    output_report: function(title, description) {
        console.log('これは HTML の出力ですよ:' + title + ':' + description);
    }

}

xmlFormatter.prototype = {

    output_report: function(title, description) {
        console.log('これは XML の出力ですよ:' + title + ':' + description);
    }

}

var report = new Report(new htmlFormatter);
report.output_report(); // これは HTML の出力ですよ:タイトル:説明


切り替えが簡単

var report = new Report(new htmlFormatter);
report.output_report(); // これは HTML の出力ですよ:タイトル:説明
report.formatter = new xmlFormatter;
report.output_report(); // これは XML の出力ですよ:タイトル:説明


ストラテジへと渡されたコンテキスト自身の参照を使って、ストラテジがコンテキストからデータを引き出す

コンテキストとストラテジに引き渡すこのテクニックはデータの流れを単純にする一方、
コンテキストとストラテジ間の結合度を上げてしまうことにもなります。
このため、コンテキストとストラテジがお互いにもつれ合ってしまう危険があります。

var Report        = function () { this.initialize.apply(this, arguments) };
var htmlFormatter = function () {};
var xmlFormatter  = function () {};

Report.prototype = {

    initialize: function(formatter) {
        this.title       = 'タイトル';
        this.description = '説明';
        this.formatter   = formatter;
    },

    output_report: function() {
        this.formatter.output_report(this);
    }

}

htmlFormatter.prototype = {

    output_report: function(context) {
        console.log('これは HTML の出力ですよ:' + context.title + ':' + context.description);
    }

}

xmlFormatter.prototype = {

    output_report: function(context) {
        console.log('これは XML の出力ですよ:' + context.title + ':' + context.description);
    }

}

var report = new Report(new xmlFormatter);
report.output_report(); // これは XML の出力ですよ:タイトル:説明


単にコードブロックで渡す - Ruby

一体なぜ、わざわざ Proc ベースのストラテジにするのでしょうか?
理由の1つは、ストラテジのためにあえてクラスを作る必要がなくなり、
Proc オブジェクトにコードをただ包むだけにできるからです。

このことが意味するのは、クラスベースのストラテジなんか忘れてしまっていいということでしょうか?
実際にはそうでありません。コードブロックベースのストラテジは、そのインターフェースが単純で、
1つのメソッドで事足りるようなときにのみ有効に働きます。つまるところ Proc オブジェクトに対して
呼べるメソッドは call しかないのです。ストラテジにもっとやることがあるのなら、
そのときこそクラスベースのやり方で作ることになります。
しかしシンプルなストラテジで要件に合うのなら、コードブロックを使ったやり方を使うべきです。

class Report
  attr_reader :title, :description
  attr_accessor :formatter

  def initialize(&formatter)
    @title       = 'タイトル'
    @description = '説明'
    @formatter   = formatter
  end

  def output_report
    @formatter.call(self)
  end

end

HTML_FORMATTER = lambda do | context |
  puts("これは HTML の出力ですよ:#{context.title}:#{context.description}")
end

XML_FORMATTER = lambda do | context |
  puts("これは XML の出力ですよ:#{context.title}:#{context.description}")
end

report = Report.new &HTML_FORMATTER
report.output_report


単にコードブロック(closure)で渡す - JavaScript


Ruby の Proc オブジェクト自体をよくわかってないので、これでいいのかはよくわからない。

var Report = function () { this.initialize.apply(this, arguments) };
var htmlFormatter;
var xmlFormatter;

Report.prototype = {

    initialize: function(formatter) {
        this.title       = 'タイトル';
        this.description = '説明';
        this.formatter   = formatter;
    },

    output_report: function() {
        this.formatter(this);
    }

}

htmlFormatter = function(context) {
    return console.log('これは HTML の出力ですよ:' + context.title + ':' + context.description);
}

xmlFormatter = function(context) {
    return console.log('これは XML の出力ですよ:' + context.title + ':' + context.description);
}


var report = new Report(htmlFormatter);
report.output_report(); // これは HTML の出力ですよ:タイトル:説明