Polymorphism in Ruby

Learn via video courses
Topics Covered

Overview

A key idea in object-oriented programming paradigms is polymorphism. In essence, it enables objects of various types to be handled as though they are members of a single superclass. By encouraging the interchangeable use of several object types, it promotes code reuse, flexibility, and extension. Ruby has robust features for polymorphism implementation.

We shall examine the idea of polymorphism in Ruby, understand its fundamental ideas, and explore the numerous strategies for implementing polymorphism in Ruby programs in this article. We will also go over the advantages and applications of polymorphism, emphasizing how important it is in object-oriented design.

Introduction

Ruby is a renowned language for object-oriented programming because of its user-friendly syntax and adaptability. Encapsulation, inheritance, and polymorphism are key concepts in object-oriented programming. They encourage reusable and manageable code across different tasks. These notions are crucial for effective coding in large codebases.

A key component of object-oriented programming is polymorphism which enables us to write interchangeable, reusable code. It is possible to use objects from multiple classes interchangeably by utilizing a shared superclass or interface. This adaptability improves our codebase's extensibility and supports a modular architecture.

Understanding Polymorphism in Ruby

Definition of Polymorphism and its Core Principles

By enabling objects to display various behaviors depending on their unique types, polymorphism plays a crucial role in object-oriented programming. Regardless of an object's actual class, this is based on the assumption that it can be used interchangeably as an instance of a shared superclass or interface.

Polymorphism allows programmers to create more flexible and reusable programs. By enabling objects to be treated consistently regardless of their unique implementation characteristics, it encourages code modularity. When working with vast codebases or when new classes need to be added without altering current code, this flexibility is quite helpful.

The different types of polymorphism are:

  • Subtyping: The first kind of polymorphism is called subtyping. It makes it possible to assign objects from derived classes to superclass variables. Because of this, a variable with the type Superclass can contain an instance of a class that derives from the Superclass. This offers freedom in the architecture of the code and permits the construction of hierarchies in which several classes can derive and expand functionality from a common superclass.
  • Late Binding: The second type of polymorphism is late binding, also referred to as dynamic binding or method overriding. With late binding, the specific implementation of a method is determined at runtime based on the actual type of the object invoking the method. This allows different classes to define their versions of the same method, providing a way to tailor behavior to specific subclasses while still adhering to a common interface or superclass.

Polymorphism Through Inheritance and Interfaces

In Ruby, polymorphism is primarily achieved through class inheritance and interfaces. Inheritance is the process by which a class inherits properties and behaviors from its superclass. When a subclass inherits from a superclass, it automatically gains access to all the methods and attributes defined in the superclass.

Example

Output

Explanation The Shape class is defined in the code example above as a superclass that serves as an interface by announcing an abstract method area. Any class that derives from Shape is required to implement the area function.

Rectangle and circle classes, which are subclasses of Shape, offer individual area method implementations. The Rectangle class determines the area by multiplying its length and breadth, whereas the Circle class determines the area based on its radius.

We may call the area method on the Rectangle and Circle objects by treating them as instances of the Shape superclass. Based on the particular object invoking the method, its actual implementation is chosen at runtime. This illustrates how late binding works in polymorphism.

Implementing Polymorphism

Polymorphism Through Method Overriding

Method overriding in Ruby is a powerful way to achieve polymorphism. By overriding a method, a subclass can provide its own implementation, tailored to its specific needs. When the overridden method is called on a subclass object, the subclass's implementation takes precedence over the superclass's.

In Ruby, overriding a method is as simple as defining a new method with the same signature in the subclass. The method's signature (name and parameters) must match exactly. The subclass can then customize the implementation according to its requirements.

This Ruby feature which enables us to design distinct behaviors for particular subclasses while still maintaining a uniform interface is especially useful as it encourages flexibility and code reuse. As we can establish a generic behavior in the superclass and adjust it as necessary in the subclasses, it also aids in the development of modular and maintainable code.

Example

Output

Explanation In the code example above, we have a Cricketer class with an abstract method role. The Batter and Bowler classes are subclasses of cricketer and override the role method with their specific implementations.

When we invoke the role method on an instance of Batter or Bowler, the appropriate overridden method is executed based on the actual type of the object. This demonstrates how polymorphism enables objects of different classes to exhibit different behaviors while being treated as instances of a common superclass.

Polymorphism Through Method Overloading

Method overloading is a powerful technique in object-oriented programming methodology that allows developers to provide different implementations of a method based on the input it receives. This eliminates the need for creating separate method names for minorly different functionalities, resulting in more intuitive and readable code.

Although method overloading is not built-in to Ruby like it is in some other languages, developers can emulate it using several simple conventions. Utilizing optional parameters or providing default parameter values is a typical strategy. A method can be called with various sets of arguments and exhibit various behaviors if its parameters are defined with default values.

This adaptability in defining member methods and calling them ensures shorter, cleaner code. The same method name can be reused by developers while offering several functionalities depending on the supplied arguments. As comparable functionalities may be wrapped within a single method, this improves modularity and encourages the reuse and maintainability of code.

Example

Output

Explanation The Calculator class defines the add methods in the above code example. The third argument of the function has a default value.

The first call of the add method with two arguments is done when we call it with two arguments. The default value of the third argument is used here. Similar to this, the second call of the add method with three parameters is used when we call it with three arguments.

We can offer various method implementations dependent on the quantity or type of parameters due to polymorphism through method overloading. When dealing with various argument scenarios, this offers flexibility and convenience.

Polymorphism with Duck Typing

Duck Typing is another way in addition to the ones already described for implementing polymorphism in Ruby. Duck Typing enables the treatment of objects according to their behavior rather than their particular class or type. Instead of relying on the object's formal type or class hierarchy, this strategy emphasizes the object's capacity to respond to certain messages or methods.

A dynamically typed language like Ruby places more emphasis on an object's capabilities than its actual nature. Ruby determines whether an object can respond to a method call during runtime, regardless of the class or type of the object. This makes it possible for programmers to create code that executes on objects with comparable behaviors, regardless of the class or level of inheritance. The adoption of polymorphism and duck typing by Ruby promotes the writing of adaptable, reusable code.

Example

Output

Explanation In the code example above, we have a Duck1 class and a Duck2 class. Both classes define a quack method, but they are unrelated in terms of inheritance or interfaces.

The make_quack method takes an object as an argument and invokes its quack method. As long as the object being passed responds to the quack method, it can be used with the make_quack method.

The duck1 and duck2 objects, despite being instances of different classes, can both be passed to the make_quack method, demonstrating the polymorphic behavior of Duck Typing. The method invocation adapts to the specific behavior of the object being passed.

Benefits and Use Cases of Polymorphism

  • Code Reusability: Polymorphism promotes code reuse by allowing objects of different types to be treated uniformly, reducing the need for duplicate code.
  • Extensibility: Adding new subclasses or implementing new interfaces becomes easier due to the ability to interchangeably use objects based on a common superclass or interface.
  • Modularity: Polymorphism enhances modularity by decoupling code dependencies, allowing different modules to interact through shared interfaces rather than concrete implementations.
  • Flexibility: Polymorphism provides flexibility in designing systems that can accommodate different variations or extensions without modifying existing code extensively.

Conclusion

  • Polymorphism is a core principle in object-oriented programming that allows objects of different types to be treated as if they belong to a common superclass or share a common interface.
  • Polymorphism in Ruby is primarily achieved through inheritance, interfaces, method overriding, method overloading, and Duck Typing.
  • It enables code reuse, enhances modularity, and promotes extensibility in software development.
  • Polymorphism allows for the reusability of code, reducing duplication and promoting efficient development.
  • It provides flexibility by accommodating different variations or extensions without extensive modifications to existing code.
  • Polymorphism enhances modularity by decoupling code dependencies and promoting interaction through shared interfaces.
  • Duck Typing in Ruby allows objects to be treated based on their behavior rather than their specific class or type, contributing to polymorphic behavior.
  • Polymorphism simplifies the addition of new subclasses or implementations, as objects can be interchangeably used based on a common superclass or interface.