Page Content

Tutorials

Applying Polymorphism In Ruby with Inheritance & Duck Typing

Polymorphism in Ruby

The term polymorphism, which translates to “many forms,” is essential to object-oriented programming (OOP). This feature, which Ruby supports, is characterized by the dynamic resolution of methods at runtime, which permits various object types to react differently to the same message (method call).

Two main strategies for accomplishing polymorphism in Ruby are highlighted by the sources:

Inheritance-based Polymorphism (Subtyping): When subclasses override methods that were inherited from a superclass, objects of the various classes can implement distinct behavior while sharing a method signature.

Duck Typing: Ruby is a very permissive type of polymorphism where an object’s capabilities the methods it implements are prioritized over its rigid class membership.

You can also read File I/O In Ruby: Basic Operations To Advanced File Handling

Inheritance-Based Polymorphism (Method Overriding)

When a method defined by a superclass is redefined (overridden) by one or more subclasses to implement particular behavior, this is known as polymorphism through inheritance.

Invoking a method looks up the method dynamically at the moment of execution rather than statically during parsing. Method name resolution is the process of looking up the relevant definition at runtime. This dynamic lookup makes sure that the precise version of a method that is suitable for the class (or subclass) of an object is utilized when it is called on that object.

Code Example: Overriding for Polymorphism

Think of a general class called WorldGreeter and its specific subclass called SpanishWorldGreeter. Despite being inherited from the parent, the subclass overrides greeting, the primary internal method upon which it depends.

class WorldGreeter
  def greeting; "Hello"; end # Parent implementation
  def who; "World"; end
  
  # The greet method is inherited by subclasses
  def greet 
    puts "#{greeting} #{who}"
  end
end

# Greet the world in Spanish
class SpanishWorldGreeter < WorldGreeter
  def greeting                   # Override the greeting
    "Hola"
  end
end

# We call the greet method inherited from WorldGreeter, 
# but it executes the specialized SpanishWorldGreeter#greeting
SpanishWorldGreeter.new.greet 

Output

Hola World

In this example:

  • The greet method is inherited from WorldGreeter.
  • When greet is called on an instance of SpanishWorldGreeter, Ruby dynamically looks up the greeting method.
  • Since SpanishWorldGreeter defines greeting to return “Hola”, the inherited greet method ends up printing “Hola World”.
  • The behavior of the greet method varies depending on the type of object it is called on, illustrating polymorphism.

You can also read What Is Command Line Crash Course Navigation In Ruby

Duck Typing

Because of Ruby’s reputation as a fairly permissive language, polymorphism is frequently accomplished through the use of duck typing in programming.

The idea behind duck type is that if an item talks and walks like a duck, the interpreter is pleased to treat it as such. It is frequently superfluous to concentrate on types (or classes) in this paradigm; instead, you concentrate on an object’s capabilities, namely, whether it implements a particular method.

Utilizing and

If different kinds of objects react to the expected method names, duck typing enables generic code to handle them. Rather than determining an object’s class (is_a? ), you determine whether it responds to the necessary method.

By intercepting calls to undefined methods, the method_missing technique is a potent approach to manage unexpected method calls and enable duck typing.

Code Example: Duck Typing with Standard Protocol ()

Many basic functions in the Ruby library accomplish polymorphism by looking for common conversion techniques (protocols), such as to_str, to_int, or to_a. Any object that uses these methods is handled similarly to the type that is intended.

An instance of your custom class will be treated as a file descriptor integer by Ruby’s built-in File.new method if you create a class that implements to_int, illustrating polymorphism:

class Roman
  def initialize(value)
    @value = value
  end
  
  def to_int
    @value
  end
end

# This is a conceptual class to illustrate duck typing
class CustomFile
  def self.new(file)
    if file.respond_to?(:to_int)
      puts "This object responds to to_int! Its integer value is: #{file.to_int}"
    else
      puts "This object does not respond to to_int. It's treated as a string."
    end
  end
end

# Create an instance of the Roman class
file_descriptor_object = Roman.new(4)

# Use our custom class to demonstrate the check
CustomFile.new(file_descriptor_object)
# Output: This object responds to to_int! Its integer value is: 4

# Now let's try with a string
CustomFile.new("MyFile.txt")
# Output: This object does not respond to to_int. It's treated as a string.

Output

This object responds to to_int! Its integer value is: 4
This object does not respond to to_int. It's treated as a string.

It can be supplied to the generic File.new function, which handles it polymorphically based on its methods, since the Roman object supports the to_int protocol. Without the Roman class being a member of any particular inheritance tree specified by File, the method call functions as intended.

You can also read What Is The Command Line Crash Course Manipulation in Ruby

Mixins and Polymorphism

Mixins (modules included in a class via the include keyword) provide another form of polymorphism by injecting instance methods into a class hierarchy. For example, the Comparable module gives any class six comparison methods (<, >, <=, >=, ==, between?), provided that the class implements the single <=> (spaceship) operator.

A uniform, polymorphic interface for comparisons is automatically added to every object that contains Comparable and specifies <=>.

Code Example: Polymorphism via Mixins (Comparable)

class Song
  include Comparable
  
  attr_reader :name, :duration
  
  def initialize(name, duration)
    @name = name
    @duration = duration
  end
  
  def <=>(other)
    self.duration <=> other.duration
  end
end

song1 = Song.new("Title A", 200)
song2 = Song.new("Title B", 300)

puts song1 < song2  # => true 
puts song2 >= song1 # => true

Output

true
true

The Song class inherits the entire set of polymorphic comparison behaviors (<, >, between?, etc.) from the Comparable module by simply declaring the initialize and <=> methods.

You can also read Method Visibility In Ruby : Public, Private, And Protected

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index