Das Nil Objekt Entwurfsmuster

Ruby ermutigt, Duck Typing zu machen. Alles ist ein Objekt und kann sich mehr oder weniger Messages schicken. Deshalb sind Typprüfungen eher ein Code Smell. Sehr oft kann das durch Duck Typing gelöst werden. Das Objekt verhält sich einfach wie erwartet.

Nil ist besonders

Es gibt eine Ausnahme. In manchen Fällen kann eine Objekt auch NIL sein und die Lokig muss damit umgehen können.
In einem Ruby on Rails Beispiel soll es um Personen gehen, die ein Bankkonto haben. Die Models:

class Person < ApplicationRecord
  has_one :bank_account
end

class BankAccount < ApplicationRecord
  has_many :transactions

  def total
    transactions.sum :value
  end

  def in_dispo?
    total.negative?
  end
end

Das Bankkonto kann den Gesamtsaldo berechnen und auch prüfen, ob das Konto im Dispo liegt. Da allerdings einige Menschen aus bestimmten Gründen kein Bankkonto haben, wird auch dessen Vorhandensein nicht validiert.
Zunächst ganz einfach eine Person mit einem Bankkonto:

alice = Person.create name: 'Alice', bank_account: BankAccount.new
# Alice macht ein paar Transaktionen
alice.bank_account.transactions.create value: 100
alice.bank_account.transactions.create value: -4.5
alice.bank_account.total.to_s # => "95.5"
alice.bank_account.in_dispo? # => false

Aber für alle Personen, die kein Bankkonto haben, wird es sehr umständlich jedes Mal zu prüfen, ob eine Bankkonto vorhanden ist:

bob = Person.create name: 'Bob'
if bob.bank_account.present?
  bob.bank_account.total.to_s
else
  "0.0"
end

if bob.bank_account.present?
  bob.bank_account.in_dispo?
else
  false
end

Das ist ebenfalls eine Art Typprüfung. Es prüft auf Nil.
Sicher, das Delegation Entwurfsmuster kann bei solchen Situationen aushelfen. Aber wenn bestimmte Werte erwartet sind (wie 0.0 für Kumulierungen oder true/false für Prüfungen), bietet sich das Nil Objekt Entwurfsmuster an.

Nil object pattern

Die eigentlich Idee hinter dem Nil Objekt Entwurfsmuster ist es, ein Default Objekt zurückzugeben, dass sich verhält, wie das erwartete Objekt (in diesem Beispiel das Bankkonto).
Dann gibt es nur noch einen einzigen Punkt, an dem der Nil Typ geprüft wird und das Nil Objekt kann die Messages verarbeiten.
Das Bankkonto der Person ist entweder das assozierte Bankkonto oder eben das Nil Objekt:

class Person < ApplicationRecord
  has_one :bank_account

  def bank_account
    super || NilBankAccount.new
  end
end

Die Klasse NilBankAccount:

class NilBankAccount
  def total
    0.0
  end

  def in_dispo?
    false
  end
end

Mithilfe des Nil Objektes sind umständliche Nil Prüfungen nicht länger notwendig:

bob.bank_account.total.to_s # => "0.0"
bob.bank_account.in_dispo? # => false