こんにちは。
最近 リファクタリング Rubyエディション を読みました。本書はリファクタリングの様々なパターンをRubyのサンプルコードを使って解説している名著です。
実務でも活かせそうなパターンが多くあったので、特にいいなーと思ったパターンを1つずつブログにまとめています。
今回はその第5弾で「タイプコードからポリモーフィズムへ」についてまとめました。
オリジナルの Ruby のサンプルコードを使って説明しています。
第4弾の「nullオブジェクトの導入」のまとめはこちらです。
nullオブジェクトの導入【リファクタリング Rubyエディションまとめ4】 - 銀行員からのRailsエンジニア
サンプルコード
リファクタリング前
文章での説明よりもコードを見た方が分かりやすいので、早速サンプルコードを見ていきましょう。
まずはリファクタリング前のコードです。
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エディションから抜粋)
- 作者:ジェイ・フィールズ,シェーン・ハービー,マーティン・ファウラー
- 発売日: 2020/03/21
- メディア: 単行本
次回はまた違うパターンをまとめていきます!
(追記)
第6弾として「コンストラクタからファクトリメソッドへ」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com
余談
最近暑くなり、扇風機選びに悩んでいたので扇風機を題材にしたサンプルコードにしてみました。
かなり悩んだ末に、個人的なブランドレベルが10の バルミューダの扇風機を購入しました!風が心地よく、コードレスが便利で大満足でした。