How do you do polymorphism in Ruby?
Solution 1
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
}
Solution 2
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...
Solution 3
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
Solution 4
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")
Solution 5
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
Peter Ramos
Updated on June 04, 2022Comments
-
Peter Ramos about 2 years
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?
-
mikeramos almost 16 yearsSorry, but what version of Ruby is "class Cat : Animal"? Isn't it "<" for inheritance?
-
John Millikin almost 16 yearsBrent: that would be "Ruby as remembered by a Python user"
-
Peter Ramos almost 16 yearsSo, will class.name output Animal or Dog? I'm genuinely curious.
-
mikeramos almost 16 yearsYep LOL, I sometimes do "Python as remembered by a Ruby user" What's the reverse of Pythonic? Rubenesque?
-
Drew Noakes over 14 yearsI'm not a Ruby guru, but doesn't
puts
output the string? If so, this example calls it twice when callingsleep
-- once within the method, and once with its return value (whatever that would be!) -
Alex Po over 14 yearsYou're right, and
puts
returnsnil
, so the outerputs
would simplyprint
a newline, equivalent toprint("#{nil}\n")
, which is same asprint("\n")
. I didn't notice the trailing newline in my output, sorry about that. -
maček over 14 years
MakeNoise
as opposed tomake_noise
? Usingreturns
? Why not follow Ruby conventions? -
mikeramos about 14 yearsWhy should I follow a convention I dislike intensely? IMHO, underscore is ugly and impedes the flow of reading; CamelCase is much easier to read. Did you really vote me down just for that? Meh is my answer
-
bta about 14 years+1 for mixing-in the common functionality instead of inheriting it. While both methods work, this is more of "the Ruby way of doing it" (for this particular example).
-
bta about 14 years@Brent- You should follow a convention (regardless of your opinion of it) if you are writing code that other people use. They will be expecting CamelCase names to refer to classes and underscore_separated names to refer to class methods and won't know anything about your thoughts on the convention. If you are only writing code for yourself to read then that's one thing but if others are going to be using your code, go with the Principle Of Least Astonishment.
-
mikeramos about 14 years@bta - I understand where you're coming from. I'm really saying two things: First, I really don't think that a deviation from the "accepted" conventions here is worth a downvote; and second, that the "accepted" conventions are ugly and force me to choose between "being in the community" and "code beauty" (in the eye of this beholder. Yes, I use returns, because I think it makes clearer code. Yes, I use CamelCase, because I think it's easier to read. And yes, I use them here because I want to show what I think is better, and let others judge.
-
bta almost 14 years@Brent- I often use
return
as well :) I disagree with some of your opinions on style, but I agree that style here is not worth a downvote. +1 for a working solution, despite coding style. -
empz over 13 yearsI hate ruby conventions too... anyway I'm getting used to them. +1 for you anyway =)
-
YoTengoUnLCD almost 8 yearsIt should be
puts animal.make_noise; animal.sleep
, notputs animal.sleep
-
YoTengoUnLCD almost 8 yearsIt's funny that in CRuby, this is almost equivalent to inheritance: including the module animal sets the superclass of Dog to be animal, and the previous superclass of Dog to be the superclass of Animal. While this works, and may be "the ruby way of doing it", one should note that this is much less efficient than inheriting: CRuby makes a copy of the module for each class including it (for the reason stated above).