Features and Bugs Addendum

Jon Yurek

I was inspired by both Jared’s previous post and the Ruby Quiz for last Friday and wrote my FizzBuzz using the Open-Closed principle. Here’s the main program:

# fizzerbuzzer.rb
class FizzerBuzzer

  def self.filters
    @filters ||= []
  end

  Dir.glob("*_filter.rb").sort.each{|f| require f }

  def self.filter(i)
    filters.collect do |filter|
      filter.filter(i)
    end.compact.join("")
  end

  def self.fizzbuzz
    (1..100).each do |i|
      result = filter(i)
      puts result.empty? ? i : result
    end
  end
end

FizzerBuzzer.fizzbuzz

It’s built the way it is so that you can change the way the code executes without having to change fizzerbuzzer.rb yourself. It looks for files ending in _filter.rb in the same directory and executes them. The classes they implement need to respond_to? :filter and they need to place an instance of themselves into the filters array. Wonderful extensability!

As for the filters themselves, they have to be named in order so the glob picks them up the right way (because we’re not writing BuzzFizz). Here’s the filter for Fizzing.

# 01_fizz_filter.rb
class FizzFilter
  def filterable?(index)
    index % 3 == 0
  end
  def filter(index)
    "Fizz" if filterable? index
  end
end
FizzerBuzzer.filters << FizzFilter.new

And the same one for Buzzing.

# 02_buzz_filter.rb
class BuzzFilter
  def filterable?(index)
    index % 5 == 0
  end
  def filter(index)
    "Buzz" if filterable? index
  end
end
FizzerBuzzer.filters << BuzzFilter.new

There, doesn’t that feel nice and extensible? For comparison, though, here’s a much shorter one

(1..100).each do |i|
  result = []
  result << "Fizz" if i % 3 == 0
  result << "Buzz" if i % 5 == 0
  result << i if result.empty?
  puts result.join("")
end

Doesn’t the nonflexibility of that just kill you?