Rubyでデザインパターン。Strategyパターン。Design Pattern in Ruby

 

 

Rubyでデザインパターン今回はStrategyパターンです。

デザインパターンはオブジェクト指向のエッセンスが詰まったものなので本を読み進めしつつ、ここにまとめてしっかりと理解していきたいです。

 

アルゴリズムを分離するStrategyパターン

 

早速Strategyパターンのコードはこちらです。

 

class Report
  attr_reader :title, :text
  attr_accessor :formatter
  
  def initialize(formatter)
    @title = 'Monthly Report'
    @text  = ['Things are going', 'really, really well.']
    @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>")
    puts('  </head>')
    puts('  <body>')
    context.text.each do |line|
      puts("    <p>#{line}</p>")
    end
    puts('  </body>')
    puts('</html>')
  end
end

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

いきなりポンと見せられてもわからないので個別に切り出して説明していきます。

 

アルゴリズムをサブクラスに委譲

 

こちらの記事でTemplateMethodパターンを紹介しましたが、

 

TemplateMethodパターンでは、名前の通りスーパークラスのテンプレートメソッドでどういう処理をどういった順序で行うというのを規定して、個別の処理はサブクラスでメソッドをオーバーライドして利用するというものでした。

このTemplateMethodパターンは処理の順序などのロジックを親クラスにもっているので、肝心の処理の順序が変更されてしまうと親クラス、子クラス全てのモジュールに影響を与えてしまいます。

そういった問題点を解決しているのがStrategyパターンでStrategyパターンでは実際のロジックをストラテジーのクラスに委譲してしまいます。

 

class Report
  attr_reader :title, :text
  attr_accessor :formatter
  
  def initialize(formatter)
    @title = 'Monthly Report'
    @text  = ['Things are going', 'really, really well.']
    @formatter = formatter
  end
  
  def output_report()
    @formatter.output_report(self)
  end
end

TemplateMethodパターンではこのoutput_reportにtitleを出力するメソッド、bodyを出力するメソッドなど必要な処理を手続き的に書いていましたが、Strategyパターンではこれらのロジックを全てformatterとして与えられたインスタンスのoutput_reportに委譲します。

Strategyパターンでは、実際のロジックを全てストラテジークラスに委譲しているので動的に実行するロジックを切り替えるということも可能です。

report = Report.new(HTMLFormatter.new)
report.output_report

report.formatter = TextFormatter.new
report.output_report

またStrategyパターンではロジックとデータを分離していて、Reportクラスの例でいうとtitleやtextなどのデータは親クラス(Contextクラスといったりします)が保持し、実際のロジックを実行する部分で自分自身をデータとして渡します。

対象のコードは下記の部分で


 def output_report()
    @formatter.output_report(self)
 end

@formatterに注入されたクラスのメソッドに対してselfで自分自身を引き渡しています。さらにstorategy側では受け取っとたcontextからデータを取得し、実際のレポート主力時に出力を行なっています。

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

 

Rubyの特徴を生かしたlambdaでお手軽Strategyパターン

 

ここまでですと一般的なStrategyパターンとあまり代わりありませんが、
Design Patterns in Ruby (Adobe Reader) (Addison-Wesley Professional Ruby Series)
では、RubyのProcオブジェクトを使った簡単なStrategyパターンの実装も紹介してありました。

本ではQuick-and-Dirty Strategiesと書かれていましたが、Javascriptなどでも似たような考え方ができて覚えておいて損しない考え方かなと思いました。

Rubyではlambdaという記法を使うことでブロックをオブジェクト化して変数につめて使うことができます。下がその例ですが、

 

sayHello = lambda { puts 'hoge' } sayHello.call
hoge
 => nil

lamdaを使うとProcオブジェクトが生成され格納したブロックを実行する場合は、
callを利用します。

これを使ってStrategyパターンを組む場合は、さきほどのReportクラスを以下のようにします。

class Report
  def initialize(&formatter)
    @title = 'Monthly Report'
    @text = ['Things are going', 'really, really well.']
    @formatter = formatter
  end
  
  def output_report
    @formatter.call(self)
  end
end

あとは次のようにlamdaでstrategyを実装して渡してあげれば簡易strategyパターンのできあがりです。

HTML_FORMATTER = lambd do |context|
  # HTMLFormatter#output_reportの処理を書く
end

report = Report.new(HTML_FORMATTER)
report.output_report

この書き方だとクラスを使わないのでかなり簡潔にかけそうですが、
まああまり多用は避けたいですね。

 

まとめ

 

今回はStrategyパターンを紹介しましたが、Strategyパターンは委譲をベースとしたデザインパターンでTemplate Methodパターンよりは、親クラスへの依存度を下げた形で実装を行うことができます。

こうやって書くとStrategyパターンの方が優れているような書き方になってしまっていますが、それぞれのパターンには向き不向きあるのでそれぞれの良いところと悪いところをわかった上で使い分けできるとよさそうですね。

また、Rubyのlambdaを使った書き方も紹介しましたがこういう選択肢もあるよという感じで覚えておくとどこかで役にたつかもしれないですね。

では。