17
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rubyデザインパターン 2日目 : Strategy

Posted at

Rubyデザインパターン学習のために、自分なりに読書の結果をまとめていくことに決めました。第2日目はStrategyです。(http://www.amazon.co.jp/gp/product/4894712857/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4894712857&linkCode=as2&tag=morizyun00-22)

スクリーンショット 2015-07-27 11.25.28.png

 2日目 Strategy

1日目のTemplateMethodでは、大きな欠点が一つありました。それは、継承をもとに全てを構築していくことです。

GoF Rubyデザインパターンでは設計原則として、継承よりも委譲のほうが好ましいと何度も書かれています。
なぜなら、継承はクラス同士の結合度が上がってしまうからです。

  • Template method → 継承
  • Strategy → 委譲、集約

Strategyパターンで機能を切り替えたければ、コンテキスト側がストラテジオブジェクト(HTMLフォーマッター部分と平文フォーマッター部分)を実行時に選択するだけで良いのです。
コンテキストから見て、ストラテジのパーツは等価に見えるように構築することにより、パーツを付け替えるようにコードを利用できるようになります。

※コンテキストとは、ストラテジを利用する側

class Report
  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(formatter)
    @title = '月次報告'
    @text = ['順調', '最高の調子']
    @formatter = formatter
  end

  def output_report
    @formatter.output_report(self) 
  end
end

class HTMLFormatter
  def output_report(context) 
    puts ('<html>')
    puts (' <head>')
    puts ("  <title>#{context.title}</title>")
    context.text.each do |line|
      puts ("  <p>#{line}</p>")
    end
    puts (' </body>')
    puts ('</html>')
  end
end

class PlainTextFormatter
  def output_report(context)
    puts ("*****#{context.title}*****")
    context.text.each do |line|
      puts line
    end
  end
end

report = Report.new(PlainTextFormatter.new) # or HTMLFormatter.new
report.output_report

これでStrategyパターンの基本的な構築は完了しました。

ただし、現状ではあまりRuby的なコードではない事は確かです。

それでは、Rubyの柔軟さを取り入れましょう!

class Report
  attr_reader :title, :text
  attr_accessor :formatter

  def initialize(&formatter)
    @title = '月次報告'
    @text = ['順調', '最高の調子']
    @formatter = formatter
  end

  def output_report
    @formatter.call(self)
  end
end

HTML_FORMATTER = lambda do |context|
  puts ('<html>')
  puts (' <head>')
  puts ("  <title>#{context.title}</title>")
  puts (' </head>')
  puts (' <body>')
  context.text.each do |line|
    puts ("  <p>#{line}</p>")
  end
  puts (' </body>')
  puts ('</html>')
end

PLAIN_FORMATTER = lambda do |context|
  puts "*****#{context.title}*****"
  context.text.each do |line|
    puts line
  end
end

report = Report.new(&HTML_FORMATTER) #or PLAIN_FORMATTER
report.output_report

Procを利用することにより、わざわざストラテジオブジェクトのためにクラスを作成する必要がなくなりました。必要なクラスの量が減るのは、それだけでも嬉しいですよね。
Procオブジェクトなので、引数で指定するときはアンパサンド(&)を明示的に付けましょう。

report = Report new do |context|
  puts "****#{context.title}****"
  context.text.each do |line|
    puts line
  end
end

report.output_report

こういった呼び出し方もできるみたいですが、自分は前者のほうが好きです。

 まとめ

Strategyパターンの恩恵は、ストラテジオブジェクトで手軽にアルゴリズムのパターンを増やし、切り替えられる事なので、コンテキスト側からストラテジ側がどう見えるかをしっかり考え、それにあった設計をすることが大事だと感じました。委譲をベースにしたパターンなので、サブクラスを汚染するTemplate Methodより安全である可能性が高いです。
またRubyはコードブロックを便利に扱える言語なので、RubyとStrategyは相性が良いと言えるでしょう。

17
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?