「コードの品質向上」が現在の僕の課題で、マネージャーから「デザインパターンを勉強してみては」とアドバイスをいただいたので、デザインパターンの勉強を始めることにしました。
具体的には、Rubyでデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめていきます。(目標は毎週1つ)
この本で紹介されているサンプルコードをそのまま使うのは面白くないので、オリジナルのコードで説明していこうと思います。
そもそもデザインパターンとは
一言で言うと、ソフトウェア設計の一般的な問題とその解決策をまとめたものです。
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形式やプレーンテキスト形式で出力するという現実的なサンプルコードを用いて説明されており、非常に分かりやすかったので、ご興味ある方は是非合わせてご覧ください。
(ただしこの本は絶版で、定価よりも高い値段で中古本が販売されている状況です...高かった...)
- 作者:Russ Olsen,ラス・オルセン
- 発売日: 2009/04/01
- メディア: 単行本
次回は、ストラテジーパターンをまとめる予定です。
来週も頑張ります!
(追記)
ストラテジーパターンについてまとめました!
使い所はTemplateメソッドパターンと似ており、ストラテジーパターンは継承を用いずに実現しています。
是非合わせてご覧ください。
ysk-pro.hatenablog.com