LoginSignup
52
45

More than 5 years have passed since last update.

NullObjectパターン

Last updated at Posted at 2014-07-27

いきなりGOF外のデザインパターン
けど、よく使いそうな感じだったのでメモ
プログラムデザインのためのパターン言語 が初出

どんなパータン?

よくあるこんな書き方

obj.method() if obj

あるオブジェクトが nil でなければ、メソッドを呼び出す
こういうパターンが頻出する場合、obj に nil の代わりに何もしないメソッドを持つオブジェクト を格納しておく

インターフェースだけ持って、何もしないオブジェクトを用意することで、
そのオブジェクトを使う側は、オブジェクトが生成されなかった場合を気にする必要がなくなる
→ オブジェクトの状態を意識する必要が無い
→ 結合度が下がる

実装例

Factoryパターンとの組み合わせで、インスタンスの生成が行えない場合、NullObject を返す

module Animal
  class Cat
    def sound
      puts 'meow'
    end
  end

  class Dog
    def sound
      puts 'wow'
    end
  end

  class NilAnimal # NullObject
    def sound
    end
  end

  def self.create(type)
    Animal.const_get(type.to_s.capitalize).new rescue NilAnimal.new
  end
end

[:cat, :dog, :llama].map do |type|
  Animal.create(type)
end.each(&:sound)

これにより、Animal.create を使う側は、Animal.create が nil を返す場合を意識する必要がなくなる

  • 今回ならば、each(&:sound) の所を、each{|animal| animal.sound if animal } と書かなくて済む

リファクタリング案

NullObject は、複数生成する必要はないので、Singleton にする場合が多い

require 'Singleton'

module Animal
...
  class NilAnimal
    include Singleton
    def sound
    end
  end

  def self.create(type)
    Animal.const_get(type.to_s.capitalize).new rescue NilAnimal.instance
  end
end

リファクタリング案2

個人的には、汎用の NilObject class を1つ作っちゃっても良いと思ってる

  • Animal class に無いメソッドを呼ばれるのが困る場合は、別途 Animal::NilAnimal を作るべき
require 'Singleton'

class NilObject < BasicObject
  include ::Singleton
  def method_missing(*args) # どんなメソッドが呼ばれても、nil を返す
  end

  def respond_to_missing?(*args)
    true
  end
end

module Animal
  class Cat
    def sound
      puts 'meow'
    end
  end

  class Dog
    def sound
      puts 'wow'
    end
  end

  def self.create(type)
    Animal.const_get(type.to_s.capitalize).new rescue NilObject.instance
  end
end

[:cat, :dog, :llama].map do |type|
  Animal.create(type)
end.each(&:sound)

Rails4 以降なら Object#try が使える

require "rubygems"
require "active_support/core_ext"

module Animal
  class Cat
    def sound
      puts 'meow'
    end
  end

  class Dog
    def sound
      puts 'wow'
    end
  end

  def self.create(type)
    Animal.const_get(type.to_s.capitalize).new rescue nil
  end
end

[:cat, :dog, :llama].map do |type|
  Animal.create(type).try(:sound)
end

Object#try は、第一引数を public_send して、結果が取れればその結果を、
NoMethodError ならば、nil を返すメソッド
ただし、Rails 3.x までは、NoMethodError を返すので注意

参考

基本的に使へるなら使ふべきパターン。特に、「if 文で null かどうか判定して null でなければメソッドを呼び出す」の様なパターンがいくつもある場合は設計が怪しいので null object パターンを真剣に検討した方が良い。
52
45
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
52
45