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_idfor each class using thedispatch_asmethod.
classDogincludeDoubleDispatchdispatch_as:dogdefpet#...endendclassHumanincludeDoubleDispatchdispatch_as:humanattr_accessor:namedefinitialize(name)@name=nameendend- Write concrete functions for each class you want handle
moduleSalutationsdefsalute_to_human(human)"Hi #{human.name}!"enddefsalute_to_dog(dog)dog.pet"Woof woof!"endend- Call
double_dispatchto 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
moduleSalutationsdefself.salute(somebody)somebody.double_dispatch(:salute_to,self)enddefsalute_to_human(human)"Hi #{human.name}!"enddefsalute_to_dog(dog)dog.pet"Woof woof!"endendAnd 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
moduleDoubleDispatchmoduleByClassNamemoduleClassMethodsdefdispatch_id@dispatch_id ||= self.name.split('::').last.downcaseendenddefself.included(base)base.include(::DoubleDispatch)base.extend(ClassMethods)endendendMost 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:
moduleDoubleDispatchmoduleByTableName::SequelmoduleClassMethodsdefdispatch_id@dispatch_id ||= self.table_nameendenddefself.included(base)base.include(::DoubleDispatch)base.extend(ClassMethods)endendendAnd use this logic in a single line:
classUser < Sequel::ModelincludeDoubleDispatch::ByTableName::Sequel
...
endAs 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.