Wednesday, August 8, 2012

Polymorphism in Ruby

In many object-oriented programming languages, there is support for polymorphism. This is when an object takes on multiple shapes or forms. You can read more about polymorphism here. When programmers talk about polymorphism, usually they are talking about inheritance polymorphism, which is the topic of this post.

Basic Polymorphism

In Ruby, inheritance polymorphism can be achieved doing something like the following:
class Bird
  def initialize(name)
    @name = name
  end

  def speak
    puts 'Tweet'
  end

  def fly
    puts 'Up up and away...'
  end
end
This is our base class, Bird. All other classes will inherit the methods in this class.
class Duck < Bird
  def speak
    puts "Quack I am #{@name}"
  end
end

class Penguin < Bird
  def speak
    puts "Squak I am #{@name}"
  end

  def fly
    puts 'Nope. I swim...'
  end
end
These are the classes that inherit from our base class. Ducks and Penguins are Birds. So, they will have basic methods speak() and fly(). The cool thing is we can override existing methods of the superclass from within the subclass to change the default behavior of a Bird. For example, Penguins don't tweet or fly. So, we override the base methods to better reflect the behavior of penguins.

So, now we can do something like this:
birds = [Bird.new('Tweetie'), Duck.new('Donald'), Penguin.new('Mumbo')]
birds.each do |b|
  b.fly
end

# Up up and away...
# Up up and away...
# Nope. I swim...

birds.each do |b|
  b.speak
end

# Tweet
# Quack I am Donald
# Squak I am Mumbo

The Ruby Way

In Ruby, traditional methods of polymorphism (seen above and in languages such as Java) are not widely accepted. A concept known as "duck typing" is preferred. You can read more about duck typing here.

Basically, we follow a simple rule: If it walks, swims, and quacks like a duck, I call that bird a duck

We can get the same type of "polymorphic" behavior by simply implementing each method individually in each class and including any common methods:
class Duck
  def speak
    puts "Quack I am #{@name}"
  end

  def fly
    puts 'Up up and away...'
  end
end

class Penguin
  def speak
    puts "Squak I am #{@name}"
  end

  def fly
    puts 'Nope. I swim...'
  end
end
Now, each type of Bird has its methods individually defined according to its own specific behavior. What about the shared initialize() constructor?
module Bird
  def initialize(name)
    @name = name
  end
end
We put any shared methods in a module. This way, we can include them in our classes:
class Duck
  include Bird
  ...
end

class Penguin
  include Bird
  ...
end

Do It Your Way

I have no strong opinion on whatever approach you decide to use. There are evangelists on both ends who live and die by either one. Solve your problem any way you please!

Resources:
Ruby polymorphism discussion: http://stackoverflow.com/questions/137661/how-do-you-do-polymorphism-in-ruby
The Ruby Way excerpt: http://books.google.com/books?id=ows9jTsyaaEC&pg=PA14
Ruby Learning: http://rubylearning.com/satishtalim/ruby_inheritance.html

1 comment:

  1. Very nice, thank you. Is duck typing intended to fully replace inheritance, or are there some cases where inheritance is preferred, even in ruby?

    ReplyDelete