Question

In C#, I can do this:

class Program
{
    static void Main(string[] args)
    {
        List<Animal> animals = new List<Animal>();

        animals.Add(new Dog());
        animals.Add(new Cat());

        foreach (Animal a in animals)
        {
            Console.WriteLine(a.MakeNoise());
            a.Sleep();
        }
    }
}

public class Animal
{
    public virtual string MakeNoise() { return String.Empty; }
    public void Sleep()
    {
        Console.Writeline(this.GetType().ToString() + " is sleeping.");
    }
}

public class Dog : Animal
{
    public override string MakeNoise()
    {
        return "Woof!";
    }
}

public class Cat : Animal
{
    public override string MakeNoise()
    {
        return "Meow!";
    }
}

Obviously, the output is (Slightly paraphrased):

  • Woof
  • Dog is Sleeping
  • Meow
  • Cat is Sleeping

Since C# is often mocked for its verbose type syntax, how do you handle polymorphism/virtual methods in a duck typed language such as Ruby?

Was it helpful?

Solution

edit: added more code for your updated question

disclaimer: I haven't used Ruby in a year or so, and don't have it installed on this machine, so the syntax might be entirely wrong. But the concepts are correct.


The exact same way, with classes and overridden methods:

class Animal
    def MakeNoise
        return ""
    end
    def Sleep
        print self.class.name + " is sleeping.\n"
    end
end

class Dog < Animal
    def MakeNoise
        return "Woof!"
    end
end

class Cat < Animal
    def MakeNoise
        return "Meow!"
    end
end

animals = [Dog.new, Cat.new]
animals.each {|a|
    print a.MakeNoise + "\n"
    a.Sleep
}

OTHER TIPS

All the answers so far look pretty good to me. I thought I'd just mention that the whole inheritance thing is not entirely necessary. Excluding the "sleep" behaviour for a moment, we can achieve the whole desired outcome using duck-typing and omitting the need to create an Animal base class at all. Googling for "duck-typing" should yield any number of explanations, so for here let's just say "if it walks like a duck and quacks like a duck..."

The "sleep" behaviour could be provided by using a mixin module, like Array, Hash and other Ruby built-in classes inclue Enumerable. I'm not suggesting it's necessarily better, just a different and perhaps more idiomatically Ruby way of doing it.

module Animal
  def sleep
    puts self.class.name + " sleeps"
  end
end

class Dog
  include Animal
  def make_noise
    puts "Woof"
  end
end

class Cat
  include Animal
  def make_noise
    puts "Meow"
  end
end

You know the rest...

Using idiomatic Ruby

class Animal
  def sleep
    puts "#{self.class} is sleeping"
  end
end

class Dog < Animal
  def make_noise
    "Woof!"
  end
end

class Cat < Animal
  def make_noise
    "Meow!"
  end
end

[Dog, Cat].each do |clazz|
  animal = clazz.new
  puts animal.make_noise
  animal.sleep
end

Building on the previous answer, is this how you might do it?


Second cut after clarification:

class Animal
    def MakeNoise
        raise NotImplementedError # I don't remember the exact error class
    end
    def Sleep
        puts self.class.to_s + " is sleeping."
    end
end

class Dog < Animal
    def MakeNoise
        return "Woof!"
    end
end

class Cat < Animal
    def MakeNoise
        return "Meow!"
    end
end

animals = [Dog.new, Cat.new]
animals.each {|a|
    puts a.MakeNoise
    a.Sleep
}

(I'll leave this as is, but "self.class.name" wins over ".to_s")

The principle of duck typing is just that the object has to respond to the called methods. So something like that may do the trick too :

module Sleeping
  def sleep; puts "#{self} sleeps"
end

dog = "Dog"
dog.extend Sleeping
class << dog
  def make_noise; puts "Woof!" end
end

class Cat
  include Sleeping
  def to_s; "Cat" end
  def make_noise; puts "Meow!" end
end

[dog, Cat.new].each do |a|
  a.sleep
  a.make_noise
end

A little variant of manveru's solution which dynamic create different kind of object based in an array of Class types. Not really different, just a little more clear.

Species = [Dog, Cat]

Species.each do |specie|
  animal = specie.new   # this will create a different specie on each call of new
  print animal.MakeNoise + "\n"
  animal.Sleep
end

This is how I would write it:

class Animal
  def make_noise; '' end
  def sleep; puts "#{self.class.name} is sleeping." end
end

class Dog < Animal; def make_noise; 'Woof!' end end
class Cat < Animal; def make_noise; 'Meow!' end end

[Dog.new, Cat.new].each do |animal|
  puts animal.make_noise
  animal.sleep
end

It's not really different from the other solutions, but this is the style that I would prefer.

That's 12 lines vs. the 41 lines (actually, you can shave off 3 lines by using a collection initializer) from the original C# example. Not bad!

There's a method becomes which implements a polymorphism (by coping all instance variables from given class to new one)

class Animal
  attr_reader :name

  def initialize(name = nil)
    @name = name
  end

  def make_noise
    ''
  end

  def becomes(klass)
    became = klass.new
    became.instance_variables.each do |instance_variable|
      value = self.instance_variable_get(instance_variable)
      became.instance_variable_set(instance_variable, value)
    end

    became
  end
end

class Dog < Animal
  def make_noise
    'Woof'
  end
end

class Cat < Animal
  def make_noise
    'Meow'
  end
end

animals = [Dog.new('Spot'), Cat.new('Tom')]

animals.each do |animal|
  new_animal = animal.becomes(Cat)
  puts "#{animal.class} with name #{animal.name} becomes #{new_animal.class}"
  puts "and makes a noise: #{new_animal.make_noise}"
  puts '----'
end

and result is:

Dog with name Spot becomes Cat
and makes a noise: Meow
----
Cat with name Tom becomes Cat
and makes a noise: Meow
----
  • A polymorphism could be useful to avoid if statement (antiifcampaign.com)

  • If you use RubyOnRails becomes method is already implemented: becomes

  • Quicktip: if you mix polymorphism with STI it brings you the most efficient combo to refactor your code

I wish it helped you

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top