How do you do polymorphism in Ruby?
-
02-07-2019 - |
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?
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