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

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

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

nullオブジェクトの導入【リファクタリング Rubyエディションまとめ4】

こんにちは。

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

本書の中でいいなと思ったパターンを1つずつブログにまとめています。

今回はその第4弾で「nullオブジェクトの導入」についてまとめました。
オリジナルの Ruby のサンプルコードを使って説明しています。

第3弾の「継承から委譲へ」のまとめはこちらです。
継承から委譲へ【リファクタリング Rubyエディションまとめ3】 - 銀行員からのRailsエンジニア

f:id:ysk_pro:20200508074431p:plain

「nullオブジェクトの導入」とは

サンプルコードを見ていく前に簡単に説明します。

概要

まず「nullオブジェクト」というのは、オブジェクトが nil だった場合の処理を集めたクラスから作られたオブジェクトを指します。

オブジェクトが nil だった場合に、代わりに null オブジェクトの同名のメソッドを呼び出すことで、メソッド呼び出しするだけで処理を分けることができ、オブジェクトの nil チェックが不要になります。

※ null と nil は同じで、一般的な名称が null、Ruby内での呼び方が nil というだけです。

どんな時に使えるか

オブジェクトが nil の場合は別の値を入れる処理を繰り返している場合に有効です。

メリット

nilチェックが不要になります。

ポリモーフィズムを利用することで、よりオブジェクト指向らしいコードになります。

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

サンプルコード

リファクタリング

サンプルコードを見ていきましょう。
まずはリファクタリング前のコードです。

class Gym
  attr_reader :name, :trainer

  def initialize(name, trainer=nil)
    @name = name
    @trainer = trainer
  end
end

class Trainer
  attr_reader :name, :muscle_quantity

  def initialize(name, muscle_quantity)
    @name = name
    @muscle_quantity = muscle_quantity
  end
end

muscle_trainer = Trainer.new('マッチョトレーナー', 100)
gold_gym = Gym.new('ゴールドジム', muscle_trainer)
joyfit = Gym.new('ジョイフィット')

# それぞれのジムのトレーナー情報を出したい
gyms = [gold_gym, joyfit]
gyms.each do |gym|
  puts "#{gym.name}: #{gym.trainer&.name || 'トレーナー不在'} / 筋肉量: #{gym.trainer&.muscle_quantity || 0}"
  # nilチェックが多くなってつらい
end

見ての通り、筋肉量100のマッチョなトレーナーがいるゴールドジムとトレーナーが不在のJOYFITがあって、それぞれのジムのトレーナー情報を出力するプログラムです。

ゴールドジムのトレーナーは本当にマッチョでした。

上記のプログラムを実行すると、結果は下記の通りとなります。

ゴールドジム: マッチョトレーナー / 筋肉量: 100
ジョイフィット: トレーナー不在 / 筋肉量: 0

トレーナー情報を出力する部分で、nil チェックが多くてつらくなっていますね。

リファクタリング

「nullオブジェクトの導入」を使ってリファクタリングすると次のようになります。

class Gym
  attr_reader :name, :trainer

  def initialize(name, trainer=nil)
    @name = name
    @trainer = trainer || NoTrainer.new # trainerがnilの時にnullオブジェクト生成 
  end
end

class Trainer
  attr_reader :name, :muscle_quantity

  def initialize(name, muscle_quantity)
    @name = name
    @muscle_quantity = muscle_quantity
  end
end

class NoTrainer
  def name
    'トレーナー不在'
  end

  def muscle_quantity
    0
  end
end

muscle_trainer = Trainer.new('マッチョトレーナー', 100)
gold_gym = Gym.new('ゴールドジム', muscle_trainer)
joyfit = Gym.new('ジョイフィット')

# それぞれのジムのトレーナー情報を出したい
gyms = [gold_gym, joyfit]
gyms.each do |gym|
  puts "#{gym.name}: #{gym.trainer.name} / 筋肉量: #{gym.trainer.muscle_quantity}"
end

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

Gymクラスのコンストラクタで、trainer が nil の場合に @trainer に null オブジェクト(NotTrainer.new)を入れることによって、nil チェックではなくシンプルなメソッド呼び出しで nil の場合の条件分岐が実現できていますね。

おわりに

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

Rubyのコードで nil チェックを行なっている箇所は多いと思うので、「nullオブジェクトの導入」というリファクタリングを覚えておくとリファクタリングの引き出しが増えますね。

リファクタリング Rubyエディションでは、もう少しリアルなサンプルケースを使って説明していてとても分かりやすかったので、ご興味ある方は是非ご覧ください。

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

(追記)
第5弾として「タイプコードからポリモーフィズムへ」についてまとめました。
是非合わせてご覧ください。
ysk-pro.hatenablog.com