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

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

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

当ブログではアフィリエイト広告を利用しています

【Rubyによるデザインパターンまとめ1】テンプレートメソッド / Template Method

「コードの品質向上」が現在の僕の課題で、マネージャーからデザインパターンを勉強してみては」とアドバイスをいただいたので、デザインパターンの勉強を始めることにしました。

具体的には、Rubyデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめていきます。(目標は毎週1つ)

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

そもそもデザインパターンとは

 一言で言うと、ソフトウェア設計の一般的な問題とその解決策をまとめたものです。

1995年に出版された「オブジェクト指向における再利用のためのデザインパターン」(4人の著者のことをギャング オブ フォーと呼び、GoF本と呼ばれています)によって、デザインパターンが広まりました。

GoF本では23のパターンに名前を付け、解説がされました。

デザインパターンを理解することで、設計についての車輪の再発明を防ぐとともに、エンジニア同士の共通言語となり、設計についてのディスカッションがスムーズに進めることができるようにもなります。

[デザインパターン①] Template Method パターンとは

基底クラスに骨格となる抽象的な処理を書き、サブクラスに具体的な処理を定義するパターンです。

アルゴリズムに多様性を持たせたい場合に便利なパターンで、不変となる部分は基底クラスに書き、変わる部分はサブクラスのメソッドに定義します。

Javaなどでサポートされている抽象メソッドや抽象クラスはRubyではサポートされていないので、呼び出した時に例外を投げるようにして擬似的に抽象メソッドを実装します。

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

サンプルコード

僕は筋トレが好きなので、筋トレネタで書いてみます。

普通筋トレは、1日に全ての部位のトレーニングを行わず「胸の日」「肩の日」のようにその日鍛える部位を決めて、部位毎に集中してトレーニングを行います。

そこで、それぞれの部位の日に行うトレーニングを出力するプログラムを考えてみます。(便利)

まずは、Template Method パターンを使わずに書いてみます。

class MuscleTraning
  def execute(body_parts)
    case body_parts
    when 'chest'
      puts 'ベンチプレス!'
      puts 'チェストプレス!'
    when 'shoulder'
      puts 'ショルダープレス!'
      puts 'サイドレイズ!'
    else
      raise "#{body_parts}という部位のトレーニングはまだ知らないよ"
    end
    puts 'プロテイン摂取'
  end
end

このように実行できます。

MuscleTraning.new.execute('chest')

MuscleTraning.new.execute('shoulder')

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

ベンチプレス!
チェストプレス!
プロテイン摂取

ショルダープレス!
サイドレイズ!
プロテイン摂取

やっていることがシンプルなので、このコードで分かりやすいのでは?と思ってしまうのですが、ここからさらに「腕の日」や「脚の日」が追加される場合や、腕の日にはプロテイン摂取を行わないという条件が追加された場合を考えてみましょう。

case 文の条件分岐が長くなり、可読性が下がり、修正時に既存のコードが壊れてしまうおそれなどが出てきてしまいます。

次に Template Method パターンを使って書き換えてみるとこのようにできます。

class MuscleTraning
  def execute
    main_training
    drink_protein
  end

  def main_training
    raise '抽象メソッド(main_training)が呼び出されてるよ'
  end

  def drink_protein
    puts 'プロテイン摂取' # デフォルトではプロテインを摂取するので、基底クラスに書いています
  end
end

class ChestTraining < MuscleTraning
  def main_training
    puts 'ベンチプレス!'
    puts 'チェストプレス!'
  end
end

class ShoulderTraining < MuscleTraning
  def main_training
    puts 'ショルダープレス!'
    puts 'サイドレイズ!'
  end
end

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

ChestTraining.new.execute

ShoulderTraining.new.execute

先ほどと同様に、新しい部位が追加された場合や、特定の場合にプロテイン摂取をしないという変更が入った場合を考えてみましょう。

新たにサブクラスを作ったり、サブクラスで drink_protein メソッドを継承することで、既存の実装に影響を与えることなくシンプルに変更に対応できることが分かると思います。

さらに、1つ1つのメソッドが簡潔で可読性も高いと思います。

これが Template Method パターンです。

モジュールの extend を使ったパターン(2020/5追記)

上記の例では、継承を使って Template Method パターンを実現していましたが、モジュールの extend を使っても同じことが実現できると知ったので紹介します。

先ほどと同じ内容を、モジュールの extend を使って書き換えたコードはこちらです。

class MuscleTraning
  def execute
    stretch
    main_training
    protein
  end

  def stretch
    puts '全身のストレッチ'
  end

  def main_training
    raise '抽象メソッド(main_training)が呼び出されてるよ'
  end

  def protein
    puts 'プロテイン摂取'
  end
end

module ChestTraining
  def stretch
    puts '胸のストレッチ'
  end

  def main_training
    puts 'ベンチプレス!'
    puts 'チェストプレス!'
  end
end

module ShoulderTraining
  def main_training
    puts 'ショルダープレス!'
    puts 'サイドレイズ!'
  end
end

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

MuscleTraning.new.extend(ChestTraining).execute
MuscleTraning.new.extend(ShoulderTraining).execute

extend メソッド は引数で渡されたモジュールのインスタンスメソッドを、レシーバ(今回の例だと MuscleTraning.new)のメソッドとして追加します。また、戻り値はレシーバを返すので、この例のように execute を繋げて実行することができます。

あとはやっていることは先ほどと同じです。

Ruby は直接継承できるクラスが1つだけなので、複数のモジュールを extend したい場合にこちら方法が特に有効となります。

おわりに

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

ただの継承では と思われるかもしれませんが、これもデザインパターンの1つなのですね。「デザインパターン」というのはもっと複雑なものをイメージしていましたが、シンプルで非常に分かりやすかったです。

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

(ただしこの本は絶版で、定価よりも高い値段で中古本が販売されている状況です...高かった...)

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

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

次回は、ストラテジーパターンをまとめる予定です。

来週も頑張ります!

(追記)
ストラテジーパターンについてまとめました!
使い所はTemplateメソッドパターンと似ており、ストラテジーパターンは継承を用いずに実現しています。
是非合わせてご覧ください。
ysk-pro.hatenablog.com