マイペースなRailsおじさん

Ruby、Ruby on Rails、オブジェクト指向設計を主なテーマとして扱います。だんだん大きくなっていくRuby on Rails製プロダクトのメンテナンス性を損なわない方法を考えたり考えなかったりしている人のブログです。

RubyのNull Objectパターン

Rubyの場合はNil Objectパターンと呼ぶことになるんでしょうか?

Null Objectパターン

Null Objectパターンは、値が存在しない場合の処理を共通化したい場合に使えるデザインパターンです。

こちらの解説がわかりやすいです

Nullオブジェクトパターンの紹介と正体 - ベインのブログ

NullObjectパターン - Qiita

Rubyで実装

CutomerクラスとLicenseクラスを使って、CustomerのLicenseのname要素を表示することを考えます。CustomerがLicenseを持っているとします。Licenseは空でも良いです。

class Customer
  attr_reader :license

  def initialize(license=nil)
    @license = license
  end
end

class License
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

Coustomerの持っているLicenseの名前を表示したいときは、次のようなコードを書くことになります。

driving_license = License.new('運転免許')
customer = Customer.new(driving_license)
puts customer.license.name # => 運転免許

ここで、免許が無いときは、「免許なし」と表示したいとします。 おそらく最も単純な方法は、表示時に分岐を作ることです。

driving_license = License.new('運転免許')
customer1 = Customer.new(driving_license)
customer2 = Customer.new
puts customer1.license? ? customer1.license.name : '免許なし' # => 運転免許
puts customer2.license? ? customer2.license.name : '免許なし' # => 免許なし

このコードをいろいろな場所から使おうと思うとき、下記の懸念点があります。

  • Customerを利用する場所では、lisenceが設定されていなかった場合の分岐を書かなくては行けない
  • lisenceが設定されていなかった場合にどのような処理が走るのかは、利用する側のコードを見ないとわからない

こういう問題を解消したいとき、Null Objectパターンが有効に働くことがあります。 Nullオブジェクトパターンの紹介と正体 - ベインのブログ

存在しないことを表現するLicenseクラス、NullLicenseを作ります。Customerでは、licenseが参照された際にlicenseがない場合は、NullLicenseのインスタンスを返すようにします。

class NullLicense
  def name
    '免許なし'
  end
end

class Customer
  def initialize(license=nil)
    @license = license
  end

  def license
    @license || NullLicense.new
  end
end

これで、licenseを参照する側で分岐を書く必要がなくなりました。

driving_license = License.new('運転免許')
customer1 = Customer.new(driving_license)
customer2 = Customer.new
puts customer1.license.name # => 運転免許
puts customer2.license.name # => 免許なし

毎回インスタンスを生成しなくて良くする

Customer#licenseは、コールされるたびにNullLicense.newを生成することになるので、無駄があります。NullLicenseクラスの定数として持たせることでこの無駄な処理を行わないようにできます。

class License
  Nothing = NullLicense.new
end

class NullLicense
  def name
    '免許なし'
  end
end

class Customer
  def initialize(license=nil)
    @license = license
  end

  def license
    @license || License::Nothing
  end
end

Null Objectパターンのうれしさ

Null Objectパターンを使う利点は次の通りです

  • 値が無い場合のあるオブジェクトを利用する側で、値がない場合のことを考慮した処理を書かなくて良くなる
  • 値がない場合の処理がNull Objectにまとまる。

デメリットとして考慮する必要があるのは、次のような点です。

  • Null Objectを返す処理が追加されるので、単純なプロパティの返却ではなくなる。単にプロパティを返してほしいだけだったのにインスタンスが生成されていて、パフォーマンスのボトルネックになる、ということがあるかもしれない。
  • インターフェースを共有することになるので、Null Objectを作る対象のインターフェースが追加されたらNull Objectも追従する必要がある

Null Objectパターンは、非常に便利なのですが、オブジェクトの生成のところで少し工夫が必要になります。いろいろなところに分岐が散ってしまいそうなとき有効なパターンです。