template methodには欠点があり、そのほとんどが、「継承」の上に成り立つ手法であるということ。それについては、前回のエントリで簡単にまとめた
前々回のエントリで書いたコードの場合を考えてみよう。
Reportというクラスを基底に、HTMLReportやPlainTextReportというサブクラスとして扱うクラスを定義していると思う。
この場合、もしレポートのフォーマットを変更したくなった場合、全体を書きなおす大変な作業になりかねない。基底クラスからサブクラスまで全部書き直すか、全部を書き換えた新しいレポートオブジェクトを作らないといけなくなる(例えば、PlainTextReportFormatのようなクラス)。
そこで出てくるのがStrategyパターン。このパターンを活用すると以下の様なプログラムになる。
# -*- encoding: utf-8 -*- # Strategyパターンのごく簡単なサンプル # 継承よりも委譲の方が好ましいというGoFのアドバイス # Template Methodのようにバリエーションごとにサブクラスを作る代わりに、 # たくさんの変化に飛んだコードの塊を全て引き剥がし、それ自身をクラスに閉じ込めてしまう class Report attr_reader :title, :text attr_accessor :formatter def initialize(formatter) @title = '月次報告' @text = ['順調', '最悪の調子'] @formatter = formatter end def output_report @formatter.output_report(@title, @text) end end class Formatter def output_report(title, text) raise 'Abstract method called' end end class HTMLFormatter < Formatter def output_report(title, text) puts '<html>' puts '<head>' puts "<title>#{title}</title>" puts '</head>' puts '<body>' text.each do |line| puts "<p>#{line}</p>" end puts '</body>' puts '</html>' end end class PlainTextFormatter < Formatter def output_report(title, text) puts "***** #{title} *****" text.each do |line| puts line end end end report = Report.new(HTMLFormatter.new) report.output_report # 出力方式の切り替えも簡単 report.formatter = PlainTextFormatter.new report.output_report
このように、「別々のオブジェクトにアルゴリズムを引き出す」テクニックをStrategyパターンと呼ぶ。
同じ目的を持った一群のオブジェクトをストラテジと呼ぶ。上記のサンプルコードでは、「レポートをフォーマットすること」がそれに該当する。
そして、ストラテジの利用者をコンテキストと呼ぶ。上記のサンプルコードでは、Reportクラスがそれに該当する。
各ストラテジオブジェクトは、同じ仕事(レポートを出力する)をこなし、同じインタフェースを持つ。サンプルコードを見れば、HTMLFormatterクラスとPlainTextFormatterは完全に同じインタフェースになっているのがわかる。
これにより、クラスからストラテジを取り出すことになり、関心の分離をとてもうまく達成することができるようになった。Reportクラスは、出力形式についての一切の責任と知識を持たなくて良くなった。