MogLog

メモというか日記というか備忘録というか

『Rubyによるデザインパターン』学習ノート:Strategy

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クラスは、出力形式についての一切の責任と知識を持たなくて良くなった。