How do you do polymorphism in Ruby?

11,520

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
Share:
11,520
Peter Ramos
Author by

Peter Ramos

Updated on June 04, 2022

Comments

  • Peter Ramos
    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
    mikeramos almost 16 years
    Sorry, but what version of Ruby is "class Cat : Animal"? Isn't it "<" for inheritance?
  • John Millikin
    John Millikin almost 16 years
    Brent: that would be "Ruby as remembered by a Python user"
  • Peter Ramos
    Peter Ramos almost 16 years
    So, will class.name output Animal or Dog? I'm genuinely curious.
  • mikeramos
    mikeramos almost 16 years
    Yep LOL, I sometimes do "Python as remembered by a Ruby user" What's the reverse of Pythonic? Rubenesque?
  • Drew Noakes
    Drew Noakes over 14 years
    I'm not a Ruby guru, but doesn't puts output the string? If so, this example calls it twice when calling sleep -- once within the method, and once with its return value (whatever that would be!)
  • Alex Po
    Alex Po over 14 years
    You're right, and puts returns nil, so the outer puts would simply print a newline, equivalent to print("#{nil}\n"), which is same as print("\n"). I didn't notice the trailing newline in my output, sorry about that.
  • maček
    maček over 14 years
    MakeNoise as opposed to make_noise? Using returns? Why not follow Ruby conventions?
  • mikeramos
    mikeramos about 14 years
    Why 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
    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
    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
    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
    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
    empz over 13 years
    I hate ruby conventions too... anyway I'm getting used to them. +1 for you anyway =)
  • YoTengoUnLCD
    YoTengoUnLCD almost 8 years
    It should be puts animal.make_noise; animal.sleep, not puts animal.sleep
  • YoTengoUnLCD
    YoTengoUnLCD almost 8 years
    It'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).