銀行員からのRailsエンジニア

銀行員からのRailsエンジニア

銀行員から転身したサービス作りが大好きなRailsエンジニアのブログです。個人で開発したサービスをいくつか運営しており、今も新しいサービスを開発しています。転職して日々感じていること、個人開発サービス運営のことなどを等身大で書いていきます。

【Rubyによるデザインパターンまとめ2】ストラテジーパターン

コードの品質向上のため、Rubyデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめています。(毎週1つが目標です!)

前回の記事(テンプレートメソッドパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

この本で紹介されているサンプルコードをそのまま使うのは面白くないので、オリジナルのコードで説明しています。

今回はStrategyパターンについてまとめました。
f:id:ysk_pro:20191103103437p:plain

Strategyパターンとは

アルゴリズムの変化する部分をクラスに閉じ込めて、アルゴリズムを実行する際はそのクラスに処理を委譲するパターンです。

アルゴリズムに多様性を持たせたい場合に利用するのはTemplateメソッドパターンと同じですが、Strategyパターンは継承を使わずに実現することができます。(Templateメソッドパターンは継承を使っており、継承にはサブクラスがスーパークラスに依存してしまうというデメリットがあります)

Strategyパターンの「Strategy(戦略)」は、アルゴリズムのことです。Strategyパターンという名前は、変化するアルゴリズム(=Strategy)を、クラスに閉じ込めてそのアルゴリズムを使うオブジェクトに引き渡すことから付けられました。

文章の説明よりも実際のコードを見た方が分かりやすいと思うので、以下のサンプルコードをご覧ください。

サンプルコード

今回はスポーツネタでいってみます。

様々なスポーツの練習内容を出力するプログラムを考えてみます。(プログラム中にあるようにゴルフとバドミントンが好きでよくやっています)

Strategyパターンを用いて書いた例がこちらです。

class Sport
  attr_accessor :sport, :before_practice

  def initialize(sport)
    @sport = sport # Golfクラス・Badmintonクラスのインスタンスが入る
    @before_practice = 'ストレッチをする'
  end

  def practice
    sport.practice(self) # 詳細を知っている別のクラス(Golf,Badminton)のメソッドに処理を委譲している
  end
end

class Golf
  def practice(sport)
    puts sport.before_practice
    puts 'アイアンの練習'
    puts 'ドライバーの練習'
  end
end

class Badminton
  def practice(sport)
    puts sport.before_practice
    puts 'スマッシュの練習'
    puts 'ヘアピンの練習'
  end
end

Golfクラス・Badmintonクラスに、アルゴリズムの変化する部分を閉じ込めて、Sportクラスのpracticeメソッドの実行は、Golfクラス・Badmintonクラスに委譲しています。

こうすることによって、各スポーツの練習方法についての知識を、Sportクラスから分離することができます。

また、Sportクラスの情報(今回は スポーツ前にはストレッチをする という情報)をGolfクラス・Badmintonクラスに渡すために、practiceメソッドにSportクラス自身のインスタンスを引数として渡しています(before_practiceを引数として渡しても問題ありません)。

以下のように実行できます。
Sportクラスのインスタンス作成時の引数に、Golfクラス・Badmintonクラスのインスタンスを渡しています。

Sport.new(Golf.new).practice

Sport.new(Badminton.new).practice

実行結果は、それぞれこのようになります。

ストレッチをする
アイアンの練習
ドライバーの練習

ストレッチをする
スマッシュの練習
ヘアピンの練習


また、Procオブジェクトを利用すると、同じ内容を次のように書くこともできます。

class Sport
  attr_accessor :sport, :before_practice

  def initialize(&sport) # 引数としてブロックを受け取るので、&(アンパサンド)をつけている
    @sport = sport
    @before_practice = 'ストレッチをする'
  end

  def practice
    sport.call(self) # Procオブジェクトはcallメソッドで実行する
  end
end

こうすることで、アルゴリズムの変化する部分を閉じ込めるクラスを作る必要がなくなり(ブロックを疑似的なクラスとして扱っています)、よりシンプルにストラテジーパターンを利用することができるようになります。

次のように実行すると、先ほどと同じ実行結果となります。

Sport.new do |sport|
  puts sport.before_practice
  puts 'アイアンの練習'
  puts 'ドライバーの練習'
end.practice

Sport.new do |sport|
  puts sport.before_practice
  puts 'スマッシュの練習'
  puts 'ヘアピンの練習の練習'
end.practice

アルゴリズムの変化する部分が小さい場合は、Procクラスを使った方法の方がシンプルにStrategyパターンを実現できます。

おわりに

ここまで読んでいただきありがとうございます。

GoF(ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のこと)によれば、「継承よりも委譲の方がより柔軟で好ましい」とあるので、Templateメソッドを検討する際には、合わせてStrategyパターンも検討してみるのが良さそうです。

Rubyによるデザインパターン の中では、レポートをHTML形式やプレーンテキスト形式で出力するという実際にありそうなサンプルコードを用いて説明されていて非常に分かりやすかったので、ご興味ある方は是非合わせてご覧ください。

Rubyによるデザインパターン

Rubyによるデザインパターン

やっぱりブログを書くと、理解が深まっていいですね。先日僕が実務で書いたコードを、早速 Templateパターン or Strategyパターンを使ってリファクタリングしたくなりました。

次回は、オブザーバーパターンをまとめます。どんなパターンか楽しみです。

来週も頑張ります!

(追記)
オブザーバーパターンについてまとめました!
Strategyパターンと似たところもあり、合わせて読むことでそれぞれの理解が深まると思います。
是非合わせてご覧ください。
ysk-pro.hatenablog.com