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?