Polymorphism

Polymorphism describes the concept where objects of different types can be accessed through the same interface. This is a core concept of Object Oriented Programming.

With polymorphism, the code that handles the interaction is agnostic about the types of objects interacting ; it only cares about the objects’ public interfaces. As long as this public interface exists, the implementation of the object or even its type is irrelevent.

Note that polymorphism needs intent to be polymorphic. Otherwise they are just two objects which have a behavior of the same name (not a common behavior).

Achieve Polymorphism in Ruby

There are three ways to achieve polymorphism in Ruby.

Polymorphism through Inheritance

The first way uses inheritance. The class that inherits is called subclass while the one which is being inherited from is the superclass.

This is probably the most common form of polymorphism.

class Shape
  def area; end
end

class Circle < Shape
  def initialize
    @r = 5
  end

  def area
    Math::PI * @r**2
  end
end

class Triangle < Shape
  def initialize
    @b = 5
    @h = 6
  end

  def area
    (@b * @h) / 2
  end
end

class Rectangle < Shape
  def initialize
    @a = 10
    @b = 8
  end

  def area
    @a * @b
  end
end

shapes = [Circle.new, Triangle.new, Rectangle.new]

shapes.each do |shape|
  puts "#{shape.class}'s area: #{shape.area}"
end

In the code above, we can treat the same way every element of the array the local variable shapes is pointing to, as long as they implement a common interface: in this case the area method (and, one can argue the class method as well).

We choose to use plane figures for our example but any object would work as lond as they implement a area method.

class Sphere < Shape
  def initialize
    @r = 5
  end

  def area
    4 * Math::PI**2
  end
end

# Passed to the array object above, this would return:
# => Sphere's area: 39.47841760435743

Let’s also imagine a shape object that doesn’t have an area in our dimension:

class AnotherDimensionObject < Shape; end

# => AnotherDimensionObject's area:

No result were output but no error were raised either. While AnotherDimensionObject does not implement a area method, its superclass Shape does.

Again, from the point of view of the code which calls area on every object, the fact that each object implements area widely differently or does not implement it but their superclass does, is irrelevent as long as the interface exists and is public (accessible).

Polymorphism through Module

Using module to achieve polymorphism is similar to inheritance. The only difference is object cannot be instanciated from a module. A module must be mixed in with a class using the include method invocation. This is called mixin.

Let’s rewrite the example from inheritance using a module:

module Shape
  def area; end
end

class Circle
  include Shape

  def initialize
    @r = 5
  end

  def area
    Math::PI * @r**2
  end
end

class Triangle
  include Shape

  def initialize
    @b = 5
    @h = 6
  end

  def area
    (@b * @h) / 2
  end
end

class Rectangle
  include Shape

  def initialize
    @a = 10
    @b = 8
  end

  def area
    @a * @b
  end
end

class Sphere
  include Shape

  def initialize
    @r = 5
  end

  def area
    4 * Math::PI**2
  end
end

class AnotherDimensionObject
  include Shape
end

shapes = [Circle.new, Triangle.new, Rectangle.new,
          Sphere.new, AnotherDimensionObject.new]

shapes.each do |shape|
  puts "#{shape.class}'s area: #{shape.area}"
end

# Circle's area: 78.53981633974483
# Triangle's area: 15
# Rectangle's area: 80
# Sphere's area: 39.47841760435743
# AnotherDimensionObject's area:

Polymorphism through Duck Typing

See Duck Typing#.