RubyのNull Objectパターン
Rubyの場合はNil Objectパターンと呼ぶことになるんでしょうか?
Null Objectパターン
Null Objectパターンは、値が存在しない場合の処理を共通化したい場合に使えるデザインパターンです。
こちらの解説がわかりやすいです
Nullオブジェクトパターンの紹介と正体 - ベインのブログ
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パターンは、非常に便利なのですが、オブジェクトの生成のところで少し工夫が必要になります。いろいろなところに分岐が散ってしまいそうなとき有効なパターンです。