OOAD

Object-Oriented Ruby: Polymorphism

posted in: OOAD, Ruby | 0

What is Polymorphism?

Polymorphism is the ability to represent different sets of data and different behaviors with a common interface. Typically, this means two classes that have the same methods but different implementations of those methods.

A good analogy is the accelerator pedal of a car. If you push on the pedal the car will go forward. Perhaps the car is an electric car; perhaps it’s a gas-powered car. But if you push on the pedal, the car goes forward. The driver doesn’t need to know how the car goes forward, just that it goes forward when you push on the pedal.

Strongly typed languages such as Java and C# allow formal definition of an interface, which other classes can then implement. Then, variables can be declared as the type of the interface, and instantiated as any class that implements it.

Polymorphism in Ruby

If you are used to a strongly typed language, you may find the approach to polymorphism of a weakly typed language such as Ruby a bit disorienting. There are no formal definitions of interfaces or abstract classes, and there is no specifying of type when declaring variables.

To accomplish polymorphic behavior, Ruby simply has subclasses override methods, or dispenses with inheritance altogether and uses duck typing.

Overriding Superclass Methods

In this example, various subclasses of Animal implement a #move method in different ways by overriding Animal‘s #move method:

We get the polymorphic behavior, but we do not get compile or runtime checking beyond an error if a method that doesn’t exist gets called, as we would in a strongly typed language with the ability to formally define interfaces and implementations of interfaces.

Duck Typing

If we don’t have any common method implementations in a superclass, we can simply use duck typing. Here’s an example:

The cars’ #rev method is an example of duck typing. We add the cars to an instance of an AutoRace class as collaborator objects. (This isn’t strictly necessary. We could just create an array of cars in main, and pass it into the #each method, and call rev from the block, and we would still have polymorphic behavior.) The AutoRace object’s #start_engines instance method calls the rev method of each of the cars in turn, getting different behavior from each of them. Polymorphic behavior, in other words.

The point in these examples is that consumers (invokers) of a polymorphic method don’t have to know exactly which object’s method they are calling. The object just has to expose that method for the consumer to call. In the above example, the start_engines method doesn’t know which object’s move method it’s calling. It just knows there is a move method.

That’s what makes polymorphism useful. It allows a consumer of a group of objects with the same method signature to be agnostic about what those objects do.