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