emancu / double_dispatch

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

License:MIT License


Languages

Language:Ruby 100.0%