Author note: This post is old and I don’t necessarily agree with everything here.
Still, the code is fun and I get a modest number of visitors for this post.
Without further ado…
I was reading Object Design a few months back and came upon an example illustrating double dispatch.
The example was showing a simple way to model Rock, Paper, Scissors. Here is the code translated to Ruby from Java:
Programmers with some experience with metaprogramming may instinctively feel uneasy with this code. I mean, it looks like a pattern. And we know that if we wished to extend the game, then we’re obligated to add another boxy class with all of those silly loses_to...
methods.
class Rock
def beats?(obj)
obj.loses_to_rock?
end
def loses_to_rock?; false; end
def loses_to_paper?; true; end
def loses_to_scissors?; false; end
end
class Paper
def beats?(obj)
obj.loses_to_paper?
end
def loses_to_rock?; false; end
def loses_to_paper?; false; end
def loses_to_scissors?; true; end
end
class Scissors
def beats?(obj)
obj.loses_to_scissors?
end
def loses_to_rock?; true; end
def loses_to_paper?; false; end
def loses_to_scissors?; false; end
end
Here’s a much better way:
# this makes me happy :)
class Piece
class << self
def beats(*args)
class_eval do
define_method(:beats) do
args.map { |s| Object.const_get(s.to_s.capitalize) }
end
end
end
end
def beats?(opp)
beats.include? opp.class
end
end
class Rock < Piece
beats :scissors
end
class Scissors < Piece
beats :paper
end
class Paper < Piece
beats :rock
end
In this way, it is trivial to extend the game:
class JamesWhiteman < Piece
beats :paper, :scissors, :rock
end
# ok, let me instantiate myself...
james_whiteman = JamesWhiteman.new
# and time to start kicking some ass :)
james_whiteman.beats? Rock.new
# => TRUE
james_whiteman.beats? Paper.new
# => TRUE
james_whiteman.beats? Scissors.new
# => TRUE