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

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

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

抽象スーパークラスからモジュールへ【リファクタリング Rubyエディションまとめ8】

こんにちは。

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

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

今回はその第8弾で「抽象スーパークラスからモジュールへ」についてまとめました。
オリジナルの Ruby のサンプルコードを使って説明しています。

前回の「サンドイッチメソッドの抽出」のまとめはこちらです。
サンドイッチメソッドの抽出【リファクタリング Rubyエディションまとめ7】 - 銀行員からのRailsエンジニア

f:id:ysk_pro:20200602083536p:plain

抽象スーパークラスからモジュールへ とは

使えるタイミング

継承関係を持っているものの、スーパークラスインスタンスを生成させたくない場合に有効です。

リファクタリング方法

スーパークラスをモジュールに書き換えて、サブクラスで include します。

このリファクタリングを使用する理由

例えば、Java ではクラスに abstract というキーワードをつけることで、そのクラスのインスタンスが生成されないようにできますが、Ruby には同じ機能はありません。

コンストラクタが呼び出された時にエラーする処理を書いても同じことは実現可能ですが、モジュールを使う方がインスタンスを生成させたくないという意図をはっきり伝えることができます

サンプルコード

リファクタリング

まずはリファクタリング前のコードです。

class Item
  def initialize(name, origin_country = nil)
    @name = name
    @origin_country = origin_country
  end

  def description
    puts "商品名:#{@name} / 補足:#{additional_description}"
  end
end

class DomesticItem < Item
  def additional_description
    '国内産です'
  end
end

class ImportedItem < Item
  def additional_description
    "#{@origin_country}で作られました"
  end
end

商品(Item)クラスを、国内産商品(DomesticItem)クラスと輸入商品(ImportedItem)クラスが継承しています。

次のように実行すると

DomesticItem.new('イヤホン').description
ImportedItem.new('スマートフォン', 'アメリカ').description

次の結果になります。

商品名:イヤホン / 補足:国内産です
商品名:スマートフォン / 補足:アメリカで作られました

このコードの問題点としては、スーパークラスである Item クラスのインスタンス化は可能なものの、Item クラスのインスタンスで description メソッドを呼び出すと additional_description メソッドが定義されていないためエラーになってしまう点です。

意図していないインスタンス生成(Item クラス)は出来ないようにしておくのが安全なので、リファクタリングを行います。

リファクタリング

「抽象スーパークラスからモジュールへ」を使ってリファクタリングをすると下記のようになります。

module Item
  def initialize(name, origin_country = nil)
    @name = name
    @origin_country = origin_country
  end

  def description
    puts "商品名:#{@name} / 補足:#{additional_description}"
  end
end

class DomesticItem
  include Item

  def additional_description
    '国内産です'
  end
end

class ImportedItem
  include Item

  def additional_description
    "#{@origin_country}で作られました"
  end
end

変更点は、継承関係を解消して、モジュール化した Item クラスをそれぞれのクラスで include したのみです。

実行の仕方、結果は先ほどと同じになります。

おわりに

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

このリファクタリングデザインパターンの1つである「テンプレートメソッドパターン」で使えそうだなーと思ったので、今回のサンプルコードは、テンプレートメソッドパターンを使ったものにしています。
テンプレートメソッドパターンについて詳しく知りたい方は、合わせてこちらをご覧ください。
ysk-pro.hatenablog.com

リファクタリング Rubyエディションでは、この記事の内容に加えてクラスメソッドが存在する場合のリファクタリングの仕方についても触れられていて参考になったので、ご興味ある方は是非ご覧ください。

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

前回の「サンドイッチメソッドの抽出」のまとめはこちらです。是非合わせてご覧ください。
ysk-pro.hatenablog.com