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

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

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

タイプコードからポリモーフィズムへ【リファクタリング Rubyエディションまとめ5】

こんにちは。

最近 リファクタリング Rubyエディション を読みました。本書はリファクタリングの様々なパターンRubyのサンプルコードを使って解説している名著です。

実務でも活かせそうなパターンが多くあったので、特にいいなーと思ったパターンを1つずつブログにまとめています。

今回はその第5弾で「タイプコードからポリモーフィズムへ」についてまとめました。
オリジナルの Ruby のサンプルコードを使って説明しています。

第4弾の「nullオブジェクトの導入」のまとめはこちらです。
nullオブジェクトの導入【リファクタリング Rubyエディションまとめ4】 - 銀行員からのRailsエンジニア

f:id:ysk_pro:20200513080734p:plain

サンプルコード

リファクタリング

文章での説明よりもコードを見た方が分かりやすいので、早速サンプルコードを見ていきましょう。
まずはリファクタリング前のコードです。

class Fan
  attr_reader :type_code, :brand_level

  def initialize(type_code, brand_level)
    @type_code = type_code
    @brand_level = brand_level
  end

  def price
    case type_code
    when :ac_motor
      brand_level * 3_000
    when :dc_motor
      brand_level * 5_000
    when :no_wing
      brand_level * 7_000
    end
  end

  # priceメソッド以外にもtypeによる条件分岐が多くなっている
end

シンプルな Fan(扇風機)クラスです。
モーターのタイプを表す type_code(タイプコード)とブランドのレベルを表す brand_level(ブランドレベル)を渡してインスタンスを作成すると、価格を返す price メソッドが使用できます。

リファクタリングの名前にも入っている「タイプコード」というのは、この例の type_code のように、インスタンスの種類を表すために注入している要素のことを指します。

次のように実行します。

nomal_fan = Fan.new(:ac_motor, 1)
puts nomal_fan.price

dyson_fan = Fan.new(:no_wing, 5)
puts dyson_fan.price

balmuda_fan = Fan.new(:dc_motor, 10)
puts balmuda_fan.price

ブランドレベルは僕の主観です(笑)

結果は下記の通りとなります。

3000
35000
50000

バルミューダの扇風機は高いですね〜。

このコードの問題点は、case 文による条件分岐を行なっているところです。

今は price メソッドにしか case 文を使っていないので悪くないように見えるかもしれませんが、他のメソッドを追加する際に同じように case 文による条件分岐を作ると、case 文のところでコードの重複が発生してしまいます。

さらに、case 文が複数存在する状態で、新たなタイプコードを追加しようとした場合は、全ての case 文に修正を入れなければならなくなります

これらが重なってくるとかなり辛い状況になってしまいます。

それでは、このコードをポリモーフィズムを使ってリファクタリングしてみましょう。

リファクタリング

「タイプコードからポリモーフィズムへ」を使ってリファクタリングすると次のようになります。

class AcMotorFan
  attr_reader :brand_level

  def initialize(brand_level)
    @brand_level = brand_level
  end

  def price
    brand_level * 3_000
  end
end

class DcMotorFan
  attr_reader :brand_level

  def initialize(brand_level)
    @brand_level = brand_level
  end

  def price
    brand_level * 5_000
  end
end

class NoWindFan
  attr_reader :brand_level

  def initialize(brand_level)
    @brand_level = brand_level
  end

  def price
    brand_level * 5_000
  end
end

タイプコードそれぞれをクラスにしそれぞれのクラスで price メソッドを定義するようにしました。

次のように実行すると結果はリファクタリング前と同じになります。

nomal_fan = AcMotorFan.new(1)
puts nomal_fan.price

dyson_fan = NoWindFan.new(5)
puts dyson_fan.price

balmuda_fan = DcMotorFan.new(10)
puts balmuda_fan.price

case 文による条件分岐がなくなりましたね。

新たなメソッドを追加する際は、それぞれのクラスに新しいメソッドを書いていくだけでいいのでシンプルです。
また、タイプコードの追加もクラスを新たに作るだけで良くなりました。

「タイプコードからポリモーフィズム」という名前はそのままで分かりやすいですね。

おわりに

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

case文を使っている箇所を見つけたら「タイプコードからポリモーフィズム」が適用できないかをまず考えてみると良さそうですね。

リファクタリング Rubyエディションでは、様々なタイプのマウンテンバイクを題材に解説されていて分かりやすかったので、ご興味ある方は是非ご覧ください。

また、ポリモーフィズムについて本書に分かりやすい説明があったので引用します。

ポリモーフィズムの本質は、オブジェクトにどのようなタイプ(型)なのかを尋ね、その答に基づいてなんらかのメソッドを呼び出すようなことをせずに、単純にメソッドを呼び出すことである。呼び出されたオブジェクトは、自分の型に基づいて適切な処理を行う。
リファクタリング Rubyエディションから抜粋)

次回はまた違うパターンをまとめていきます!

(追記)
第6弾として「コンストラクタからファクトリメソッドへ」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com

余談

最近暑くなり、扇風機選びに悩んでいたので扇風機を題材にしたサンプルコードにしてみました。
かなり悩んだ末に、個人的なブランドレベルが10の バルミューダの扇風機を購入しました!風が心地よく、コードレスが便利で大満足でした。