Skip to content

Latest commit

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

double_dispatch

Maintainability

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.

Usage

  1. Define a unique dispatch_id for each class using the dispatch_as method.
classDogincludeDoubleDispatchdispatch_as:dogdefpet#...endendclassHumanincludeDoubleDispatchdispatch_as:humanattr_accessor:namedefinitialize(name)@name=nameendend
  1. 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
  1. 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!"

Common patterns

Create modules to encapsulate the logic

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!"endend

And then, we use the module in a cleaner way:

Salutations.salute Dog.new
# => "Woof woof!"
Salutations.salute Human.new("Emiliano")
# => "Hi Emiliano!"

Use class.name as dispatch_id

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)endendend

Use table's name as dispatch_id

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:

moduleDoubleDispatchmoduleByTableName::SequelmoduleClassMethodsdefdispatch_id@dispatch_id ||= self.table_nameendenddefself.included(base)base.include(::DoubleDispatch)base.extend(ClassMethods)endendend

And use this logic in a single line:

classUser < Sequel::ModelincludeDoubleDispatch::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.

About

Method overloading with runtime types made easy, using double dispatch in Ruby

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages