Skip to content

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

License

Notifications You must be signed in to change notification settings

emancu/double_dispatch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

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.
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
  1. 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
  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

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

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

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

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:

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.

About

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

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages