Call different functions depending on the runtime types of two objects. Extremely simple to use and extend.
I personally use it to compensate the lack of method overloading in Ruby and
separate concerns into smaller modules
.
- Define a unique
dispatch_id
for each class using thedispatch_as
method.
class Dog
include DoubleDispatch
dispatch_as :dog
def pet
#...
end
end
class Human
include DoubleDispatch
dispatch_as :human
attr_accessor :name
def initialize(name)
@name = name
end
end
- Write concrete functions for each class you want handle
module Salutations
def salute_to_human(human)
"Hi #{human.name}!"
end
def salute_to_dog(dog)
dog.pet
"Woof woof!"
end
end
- Call
double_dispatch
to handle different non-necessary-polymorphic objects.
Dog.new.double_dispatch(:salute_to, Salutations)
# => "Woof woof!"
Human.new("Emiliano").double_dispatch(:salute_to, Salutations)
# => "Hi Emiliano!"
This is my favourite pattern.
Using the same example described above, we can create a better internal API if we
encapsulate all the salutation logic into a single module
module Salutations
def self.salute(somebody)
somebody.double_dispatch(:salute_to, self)
end
def salute_to_human(human)
"Hi #{human.name}!"
end
def salute_to_dog(dog)
dog.pet
"Woof woof!"
end
end
And then, we use the module in a cleaner way:
Salutations.salute Dog.new
# => "Woof woof!"
Salutations.salute Human.new("Emiliano")
# => "Hi Emiliano!"
I frequently find myself using the same dispatch_id
as the class name, so
I used to extend DoubleDispatch
with the following snippet
module DoubleDispatch
module ByClassName
module ClassMethods
def dispatch_id
@dispatch_id ||= self.name.split('::').last.downcase
end
end
def self.included(base)
base.include(::DoubleDispatch)
base.extend(ClassMethods)
end
end
end
Most of the time, we will use Active Record objects (or Sequel models, etc) in our system and we want to identify these models by the table name.
Since this gem is flexible and easy to extend, I suggest to extend DoubleDispatch
with a specific module using the ORM-specific methods.
For example, an extension for Sequel
models would be:
module DoubleDispatch
module ByTableName::Sequel
module ClassMethods
def dispatch_id
@dispatch_id ||= self.table_name
end
end
def self.included(base)
base.include(::DoubleDispatch)
base.extend(ClassMethods)
end
end
end
And use this logic in a single line:
class User < Sequel::Model
include DoubleDispatch::ByTableName::Sequel
...
end
As you can see, it won't need to call dispatch_as
method, but you can always
call it and overwrite the dispatch_id
. This is extremely useful when you define
more than a model over the same table name.