blog

紫陽花

Strategyパターン Ruby

Strategyパターン

ストラテジーパターンとは

抽象的な処理と具象的な処理を分離することで、 変化に強い構造を実現する。

委譲を用いてアルゴリズムを変更可能にする。

目的の部分としてはテンプレートメソッドパターンと似ているが、

問題に対する解決のアプローチが異なる。

ストラテジは「戦略」という意味。

条件によってアルゴリズムを切り替えるところは、まるで戦略を練っているようだからその名がついたそうな。

ストラテジーパターンの最終的な目的は、 アルゴリズムの交換」を行うこと。

テンプレートメソッドパターンが抱える問題

テンプレートメソッドパターンは 継承をベースにしているといこと。

これによりよろしくないつながりを生んでしまう。

良い部分を作り出すと同時にそこで生まれたつながりが複雑にしてしまう。

よろしくないつながりを生み出さずに変化しやすい形をとりたい。

そんなときに使うのがStrategyパターン

変化しやすいコードの塊を抽出し、 全く別のクラスに閉じ込める。

Strategyパターンでは、アルゴリズムの部分を他の部分と意識的に分離する。

そしてアルゴリズムとインターフェースを規定し、アルゴリズムの部分は委譲を用いて処理を他クラスの使って解決する。

これによりアルゴリズムは代替可能な状態になるため変更が容易になる。

アルゴリズムのクラス化」

構成

  • コンテキスト(Context):ストラテジの利用者
  • 抽象戦略(Strategy):同じ目的をもった一連のオブジェクトを抽象化したもの
  • 具象戦略(ConcreateStrategy):具体的なアルゴリズム

用いるメリット

  • 使用するアルゴリズムに多様性を持たせることができる
  • コンテキストと戦略を分離することでデータも分離することができる
  • 継承よりもストラテジを切り替える方が楽

実装

  • Report(コンテキスト):レポートを表す
  • Formatter(抽象戦略):レポート
  • HTMLFormatter(具象戦略):HTMLフォーマットでレポートを出力
  • PlaneTextFormatter(具象戦略):PlaneTextフォーマットでレポートを出力

f:id:Yamakichi:20161121000756p:plain

処理をstrategy(戦略)のclassとして定義する。 Formatterクラスを抽象戦略として定義しているが、これはインターフェースを定義するだけのクラスになるので、 Rubyらしくはありません。不要なら定義しなくても問題はない(ダックタイピングの哲学)

# レポートの出力を抽象化したクラス(抽象戦略)
class Formatter
  def output_report(title, text)
    raise 'Called abstract method !!'
  end
end

# HTML形式に整形して出力(具体戦略)
class HTMLFormatter < Formatter
  def output_report(report)
    puts "<html><head><title>#{report.title}</title></head><body>"
    report.text.each { |line| puts "<p>#{line}</p>" }
    puts '</body></html>'
  end
end

# PlaneText形式(*****で囲う)に整形して出力(具体戦略)
class PlaneTextFormatter < Formatter
  def output_report(report)
    puts "***** #{report.title} *****"
    report.text.each { |line| puts(line) }
  end
end

レポート側の実装

# レポートを表す(コンテキスト)
class Report
  attr_reader :title, :text
  attr_accessor :formatter

    # 引数にformatterオブジェクトを渡す(依存オブジェクトの注入)
  def initialize(formatter)
    @title = 'report title'
    @text = %w(text1 text2 text3)
    @formatter = formatter
  end

    # @formatterのoutput_reportに処理を委譲する
  def output_report
    @formatter.output_report(self)
  end
end

report = Report.new(HTMLFormatter.new)
report.output_report
#<html><head><title>report title</title></head><body>
#<p>text1</p>
#<p>text2</p>
#<p>text3</p>
#</body></html>

report.formatter = PlaneTextFormatter.new
report.output_report
#***** report title *****
#text1
#text2
#text3

注意すべきこと

  • コンテキストとストラテジ間のインターフェースがストラテジの種類の増加を妨げないようにすること
  • コンテキストの変更がストラテジに影響を与えないようにすること