Polymorphism - the provision of a single interface to entities of different types

Polymorphism is one of the fundamental features of object oriented programming, but what exactly does it mean? At its core, in Ruby, it means being able to send the same message to different objects and get different results. Let’s look at a few different ways to achieve this.

One way we can achieve polymorphism is through inheritance. Let’s use the template method to create a simple file parser.

First let’s create a GenericParser class that has a parse method. Since this is a template the only thing this method will do is raise an exception:

class GenericParser def parse raise NotImplementedError , 'You must implement the parse method' end end

Now we need to make a JsonParser class that inherits from GenericParser :

class JsonParser < GenericParser def parse puts 'An instance of the JsonParser class received the parse message' end end

Let’s create an XmlParser to inherit from the GenericParser as well:

class XmlParser < GenericParser def parse puts 'An instance of the XmlParser class received the parse message' end end

Now let’s run a script and take a look at how it behaves:

puts 'Using the XmlParser' parser = XmlParser . new parser . parse puts 'Using the JsonParser' parser = JsonParser . new parser . parse

The resulting output looks like this:

Using the XmlParser An instance of the XmlParser class received the parse message Using the JsonParser An instance of the JsonParser class received the parse message

Notice how the code behaves differently depending on which child class receives the parse method. Both the XML and JSON parsers modify GenericParser ‘s behavior, which raises an exception.

In statically typed languages, runtime polymorphism is more difficult to achieve. Fortunately, with Ruby we can use duck typing.

We’ll use our XML and JSON parsers again for this example, minus the inheritance:

class XmlParser def parse puts 'An instance of the XmlParser class received the parse message' end end class JsonParser def parse puts 'An instance of the JsonParser class received the parse message' end end

Now we’ll build a generic parser that sends the parse message to a parser that it receives as an argument:

class GenericParser def parse ( parser ) parser . parse end end

Now we have a nice example of duck typing at work. Notice how the parse method accepts a variable called parser . The only thing required for this to work is the parser object has to respond to the parse message and luckily both of our parsers do that!

Let’s put together a script to see it in action:

parser = GenericParser . new puts 'Using the XmlParser' parser . parse ( XmlParser . new ) puts 'Using the JsonParser' parser . parse ( JsonParser . new )

This script will result in the following output:

Using the XmlParser An instance of the XmlParser class received the parse message Using the JsonParser An instance of the JsonParser class received the parse message

Notice that the method behaves differently depending on the object that receives it’s message. This is polymorphism!

We can also achieve polymorphism through the use of design patterns. Let’s look at an example using the decorator pattern:

class Parser def parse puts 'The Parser class received the parse method' end end

We need to change our XmlParser to include a constructor that accepts a parser as an argument. The parse method will need to be modified to send the parse message to the parser it receives when constructed:

class XmlParser def initialize ( parser ) @parser = parser end def parse @parser . parse puts 'An instance of the XmlParser class received the parse message' end end

We’ll make the same change to our JsonParser :

class JsonParser def initialize ( parser ) @parser = parser end def parse puts 'An instance of the JsonParser class received the parse message' @parser . parse end end

We’ll use the decorators to create our normal XML and JSON parsers, but in the last example, we’ll do something a little different: use both decorators to achieve runtime polymorphism:

puts 'Using the XmlParser' parser = Parser . new XmlParser . new ( parser ). parse puts 'Using the JsonParser' JsonParser . new ( parser ). parse puts 'Using both Parsers!' JsonParser . new ( XmlParser . new ( parser )). parse

This script will give us the following output:

Using the XmlParser The Parser class received the parse method An instance of the XmlParser class received the parse message Using the JsonParser An instance of the JsonParser class received the parse message The Parser class received the parse method Using both Parsers! An instance of the JsonParser class received the parse message The Parser class received the parse method An instance of the XmlParser class received the parse message

Notice how we’re able to change the results of sending the parse message based on the output.

Now we will look at an intentionally simple example of how taking advantage of polymorphism can simplify our code. Let’s say we had the following classes:

class Parser def parse ( type ) puts 'The Parser class received the parse method' if type == :xml puts 'An instance of the XmlParser class received the parse message' elsif type == :json puts 'An instance of the JsonParser class received the parse message' end end end

We can simplify our Parser class by removing the branch logic thanks to simple duck typing. In this particular example we also get the benefit of separating concerns. Now we have our specific parsing logic encapsulated within their own classes:

class Parser def parse ( parser ) puts 'The Parser class received the parse method' parser . parse end end class XmlParser def parse puts 'An instance of the XmlParser class received the parse message' end end class JsonParser def parse puts 'An instance of the JsonParser class received the parse message' end end

This example demonstrates that we were able to simplify our class using polymorphism. We were also able to satisfy the Single Responsibility Principle.

In the initial version of the code the Parser class determined which parser to use and then initiated the parsing by instantiating and sending the parse message to the object. In the latter version this class only sends the parse method to kick off the process.

Polymorphism is one of the foundational elements of object oriented programming, but can also be confusing to get a grasp on. Taking the time to understand it and why it’s important is a big step towards writing more maintainable and extensible code.

If you found this useful, you might also enjoy: