Engineering

Sep 02, 2018

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 パターンを紹介しましたが、

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

 

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 を使った書き方も紹介しましたがこういう選択肢もあるよという感じで覚えておくとどこかで役にたつかもしれないですね。

では。

関連記事

記事検索

気になるサイト内の記事を検索する

プロフィール

バンクーバー在住のフルスタックエンジニアです。React, Ruby on Rails, Go などでお仕事しています。職場がトロントなので日本、トロント、バンクーバーの三つの時天空を操って生活しています。

プロモーション