Last Updated: February 25, 2016
·
713
· jeffects

Replace conditional with Null Object

Null Objects

Null objects are objects that define neutral/null behavior. In many object-oriented languages, references may be null/nil. These references must be check before actually calling a method because methods typically cannot be invoked on nil references.

Null Object are used to express the absence of an object, for example, no subscription. A Null Object implements the expected interface and the expected behavior for the absence of an object of interest.

By returning a null object, there is no need to check conditionals. Calling that method will simply do nothing.

When to use:

  • when one notices a lot of duplication in code and logic. It's noticeable when we are constantly checking for nil.

Pros:

  • Removes clutter
  • Improves readability of the code
  • Removes conditionals

Cons:

  • Need to create a null class that implements the expected interface of the class of interest
  • More code = more maintenance

Without Null Object

class User
  include ActiveModel::Model
  attr_accessor :credit_card, :subscription

  def charge
    unless subscription.nil?
      subscription.charge(credit_card)
    end
  end

  def price
    subscription.try(:price) || 0
  end
end

With Null Object

class NoSubscription
  def price
    0
  end

  def charge(credit_card)
  end
end

class User
  include ActiveModel::Model
  attr_accessor :credit_card, :subscription

  def subscription
    @subscription || NoSubscription.new
  end

  def charge
    subscription.charge(credit_card)
  end

  def price
    subscription.price
  end
end

2 Responses
Add your response

Really nice approach. You can clean up User#price if you do something like this:

class NoSubscription
  def charge
  end

  def price
  end
end

class User
  include ActiveModel::Model
  attr_accessor :credit_card, :subscription

  def subscription
    @subscription || NoSubscription.new
  end

  def charge
    subscription.charge(credit_card)
  end

  def price
    subscription.price
  end
end

However, I would be careful adding wrapper methods like User#charge and User#price because this tends to lead to God models, especially when working with classes like User, Order, etc.

In general though, your suggestion prevents nil checks and removes conditionals, and that's an excellent improvement!

over 1 year ago ·

Thanks @chip. I made your suggested changes. I also agree with you with the God models. I should make a post in regards to God classes.

over 1 year ago ·