Strategy
Rubyによるデザインパターン生活2日目。
今日はStrategyパターンを学びます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
アルゴリズムの一部を変化したい場合の対処法としてTemplete methodを習いました。
Templete methodではベースとなるアルゴリズムを実装したスーパークラスを継承しなくてはなりません。
Rubyは単一継承しかできないので、継承を頻繁に利用することは好ましくありません。
可能であれば、何事も継承より委譲で実装すべきです。
そこで、Strategyパターンの出番ですよ、と。
Strategyパターンは委譲によりアルゴリズムを切り替えることができます。
それでは、コードを見ていきます。
登場するクラス
- Reportクラス
- レポート出力するクラス。出力処理の詳細アルゴリズムを委譲する。
- HTMLReportFormatクラス
- HTMLレポート出力アルゴリズムを実装したクラス。
- PlainReportFormatクラス
- プレーンレポート出力アルゴリズムを実装したクラス。
まずはReportクラス。
class Report def initialize(format) @title = "日記" @text = ["今日は大阪に行ってきました。", "天気は晴れでした。"] @format = format end def output_report @format.print(@title, @text) end end
コンストラクタに出力フォーマット用のオブジェクトを受け取ります。
output_reportメソッドではフォーマット用のオブジェクトに処理を委譲しています。
次にHTMLReportFormatクラスとPlainReportFormatクラスです。
class HtmlReportFormat < Format def initialize() end def print(title, text) puts "<html>" puts "<head><title>#{@title}</title></head>" puts "<body>" text.each { |line| puts "<p>" + line + "</p>"} puts "</body>" puts "</html>" end end
class PlainReportFormat < Format def initialize end def print(title, text) puts "***** #{title} *****" text.each { |line| puts line } end end
実行するためのプログラムです。
puts "HTML REPORT--------" report = Report.new(HtmlReportFormat.new) report.output_report puts "PLAIN REPORT--------" report = Report.new(PlainReportFormat.new) report.output_report
そんで実行結果です。
HTML REPORT-------- <html> <head><title></title></head> <body> <p>今日は大阪に行ってきました。</p> <p>天気は晴れでした。</p> </body> </html> PLAIN REPORT-------- ***** 日記 ***** 今日は大阪に行ってきました。 天気は晴れでした。
とまぁ実行結果は前回と同じです。ちょっとソースを解説してみます。
RubyはJavaと違って厳密な型定義は必要としません。
そのため、HTMLReportFormatクラス、PlainReportFormatクラスにスーパークラスを定義する必要がありません。*1
Strategyパターンでは、アルゴリズムを委譲する側のクラス(Reportクラス)をContextと呼び、
アルゴリズムを実装する側のクラス(HTMLReportFormatクラス、PlainReportFormatクラス)をStrategyと呼ぶようです。
また、今回はCotextからStrategyへパラメータを受け渡す際に、
def output_report @format.print(@title, @text) end
のように2つの引数を渡しています。
これは次のように置き換えても構いません。*2
def output_report @format.print(self) end
これはStrategyの引数に自身を示すオブジェクトを渡すことで、
引数をまとめちゃうテクニックです。
この場合のトレードオフとして、ContextとStrategy間の結合度が強くなってしまいます。
で、RubyのStrategyパターンのお話はここまでで前半戦終了です。
JavaのエンジニアならここまででOK!な気持ちになりますが、
RubyだとこんなふうにStrategyをコードブロックで渡すこともできます。
Reportクラスを次のように変更します。
class Report attr_reader :title, :text def initialize(&format) @title = "日記" @text = ["今日は大阪に行ってきました。", "天気は晴れでした。"] @format = format end def output_report @format.call(self) end end
コンストラクタにコードブロックを引数にとるように修正しました。
すると、呼び出しもとでStrategyを作成することができます。
呼び出しもとプログラム
require "report" #format HTML_FORMATTER = lambda do |context| puts "<html>" puts "<head><title>#{context.title}</title></head>" puts "<body>" context.text.each { |line| puts "<p>" + line + "</p>"} puts "</body>" puts "</html>" end PLAIN_FORMATTER = lambda do |context| puts "***** #{context.title} *****" context.text.each { |line| puts line } end puts "HTML REPORT--------" report = Report.new &HTML_FORMATTER report.output_report puts "PLAIN REPORT--------" report = Report.new &PLAIN_FORMATTER report.output_report
なんとなくRubyっぽいプログラムになりました。
実行結果はさきほどと同じになります。
Rubyの場合は実行時にStrategyアルゴリズムを動的に作ることもできそうです。
勉強になりました。