Intro

Another great section in Sandi Metz’ <a href”https://www.sandimetz.com/99bottles/”> 99Bottles of Object Oriented Programming </a>, Metz introduces the practice of replacing conditionals with polymorphism. For those who are not familiar with polymorphism, it simply refers to the ability of having many different types of objects respond to a particular message. Metz states, “Polymorphism allows senders to depend on the message while remaining ignorant of the type, or class, of the receiver. Senders don’t care what the receivers are; instead, they depend on what receivers do.” Given that explanation let’s explore replacing conditionals with polymorphism.

Code Sample Analysis

If you remember me mentioning before that this book is focused around writing a clean/tested version of the song, “99 Bottles of Beer on the Wall.” Below is the class that controls what text is returned during certain times of the main verse of the song.

class BottleNumber
  attr_reader :number
  def initialize(number)
    @number = number
  end

  def container
    if number == 1
      "bottle"
    else
      "bottles"
    end
  end

  def quantity
    if number == 0
      "no more"
    else
      number.to_s
    end
  end

  def action
    if number == 0
      "Go to the store and buy some more"
    else
      "Take #{pronoun} down and pass it around"
    end
  end

  def pronoun
    if number == 1
      "it"
    else
      "one"
    end
  end

  def successor
    if number == 0
      99
    else
      number - 1
    end
  end
end

Notice the container method where if number equals 1, meaning there is one bottle of beer left, that we are to return “bottle” as oppossed to “bottles.” Now, notice that we have similar conditionals for all the methods where number is equal to 0 or 1. This will allow us to abstract out two classes to remove these conditionals.

Using Polymorphism

Now, since we have identified some similarities in our conditionals we can isolate them. Let’s just focus on the quantity method. We could create a BottleNumber0 class and set the quantity to be returned as the string "no more". We could do this through inheritance and implementing a single conditional. We could do that with the following:

Class Bottles
  def verse(number)
    bottle_number = bottle_number_for(number)
    next_bottle_number = bottle_number_for(bottle_number.successor)
    # Verse Logic
  end

  def bottle_number_for(number)
    if number == 0
      BottleNumber0
    else
      BottleNumber
    end.new(number)
  end
end

class BottleNumber0 < BottleNumber
  def quantity
    "no more"
  end
end

Metz creates a factory here where the correct object is created dependent on the number in the bottle_number_for method. You can also see that the new BottleNumber0 class inherits quantity from BottleNumber and overrides the method. This is a great use of polymorphism, which can get rid of every instance where a conditional uses the value “0” to make a decision in BottleNumber.

Conclusion

Using polymorphism to reduce the amount of conditionals you have in your code, really does help with decreasing the size of your classes, but also helps with code quality and readability. If one, wanted to add a feature to the song, this methodology could help, instead of adding conditionals on top of conditionals in the BottleNumber class. An example would be if someone wanted the song to say “Six Pack” for the number 6. You can do this easily with utilizing the following technique.