Posted at

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

More than 3 years have passed since last update.

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は相性が良いと言えるでしょう。