DEV Community

Jon Lunsford
Jon Lunsford

Posted on

Refactoring Ruby: Introduce Null Object

Introduce Null Object is a great refactoring tool when you find yourself checking for nil too often. nil checks are often signs of much larger problems, as it violates Tell, Don’t Ask, and results in defensive, bug prone programming. Let’s take a look at a simple example of refactoring nil checks:

class Movie
  attr_accessor :account

  def initialize(price)
    @price = price
  end

  def rent
    unless account.nil?
      account.charge(@price)
    end
  end

  def rentable?
    account && account.rentable?
  end

  def late_fees
    account.try(:late_fees) || 0
  end
end
Enter fullscreen mode Exit fullscreen mode

This Movie class is incredibly defensive, it goes to great lengths to ensure its account actually exists. Why should that responsibility fall on Movie? It would be much better for Movie to just assume it has an account OR something resembling an account. Let’s Introduce Null Object:

class Movie
  attr_accessor :account

  def initialize(price)
    @price = price
  end

  def rent
    account.charge(@price)
  end

  def rentable?
    account.rentable?
  end

  def late_fees
    account.late_fees
  end

  def account
    # Introduce Null Object
    @account ||= NullAccount.new
  end
end

# Object that resembles an 'Account'
class NullAccount
  def charge(_price)
    "No Charge"
  end

  def rantable?
    false
  end

  def late_fees
    0
  end
end
Enter fullscreen mode Exit fullscreen mode

We have gotten rid of all conditions defending against nil and introduced one that always returns an Account like object:

def account
  @account ||= NullAccount.new
end
Enter fullscreen mode Exit fullscreen mode

Let’s run through the exact steps to take when applying Introduce Null Object:

  1. Identify conditions that check for nil, that are defending against objects potentially not existing, for example:
def rent
  # nil check
  unless account.nil?
    account.charge(@price)
  end
end
Enter fullscreen mode Exit fullscreen mode
  1. Create an object that acts like the one you are checking for as nil. Specifically, create a new class and define all of the methods that are expected to be there, for example:
class NullAccount
  def charge(_price)
    "No Charge" 
  end
end
Enter fullscreen mode Exit fullscreen mode
  1. Finally, substitute the null object when the expected one does not exist, for example:
Class Movie
  ...
  def account
    @account ||= NullAccount.new
  end
end
Enter fullscreen mode Exit fullscreen mode

Null objects are great at replacing conditional logic and making code more readable, as you don’t have to think through all of the different branches conditions introduce.

Top comments (0)