OOP with D&D: Polymorphism

Using D&D to Understand the Four Pillars of OOP, Part III

BenMauss
Level Up Coding

--

Last week, we discussed Abstraction, which is the ability for a developer to only provide the details necessary to use the various classes and their methods without exposing them to the underlying code. This provides protection to your source code, as well as saving the user time since they don’t have to reinvent the wheel whenever they want to use your methods, classes, packages, etc. It’s a pretty simple concept!

We also discussed abstract classes, which are classes that have at least one undefined method. In Python, you first need to import ABC (acronym for Abstract Base Class) and abstractmethod from the abc package. We declare our class as abstract by essentially making our class a pseudo-child of the ABC class. To make an abstract method, first declare it with @abstractmethod and name your method, passing self in as an argument. We leave the method empty with pass so that other classes can set their own implementations.

The Sentient class is now an abstract class. This means that it is not instantiable. This is because abstract classes are not used as a blueprint for objects, but for other classes. Furthermore, any child of an abstract class will also be abstract until they provide an implementation for ALL abstract methods they inherit. Hence, Monster class was also an abstract class even though it was not declared as “abstract”. It was made abstract through inheritance.

Once, an implementation is provided, the class becomes concrete and instantiable, as we saw with the DragonTurtle class:

This idea of how a child class can provide implementation for a method (abstract or not) provides a good segue into today’s topic: Polymorphism.

Update April 12, 2021: A special thanks to Angelo Hulshout for taking the time to clarify the definition of polymorphism to me. Before, I misspoke, stating that methods could “share the same name but be used for different purposes.” This is very poor practice. After all, you wouldn’t want two methods called add() and have one of them print lyrics to a song! I’ve taken the liberty of updating the article accordingly with the more appropriate definition provided by Angelo.

What is Polymorphism?

Polymorphism is really weird. In programming, polymorphism is a property that allows methods to share the same name but have different implementations. Like the word’s definition (“many-forms”) suggests, polymorphism is exhibited in different ways: Dynamic and Static polymorphism.

Dynamic Polymorphism (in Python)

In Python, Dynamic Polymorphism is generally achieved through Method Overriding. This is when a child class provides its own implementation for a method that it inherited from its parent class. You may remember we did this last week with the Animal and Fox classes and the speak() method.

Here, even though we have two separate implementations for a shared method, the program doesn’t get confused as to which version to use because the implementations are tied to the object’s class. This polymorphic benefit isn’t just reserved for parent-child relationships, however, as we can see below:

Note: While this is an example of dynamic polymorphism, this is not method overriding. Method Overriding can only occur between classes with a parent-child hierarchy.

Here, we’ve stripped the classes of their hierarchal relationship, yet Python still doesn’t get confused about which version to use since, again, the method is tied to the object’s class.

In D&D

For this, let’s go ahead and make the Monster class concrete by providing an implementation for calculate_hit_points.

We based our method on a humanoid Guard, since it makes for a great baseline. Now, we’ll test that it is instantiable.

Ok! The class is now concrete and the method is working as expected. Now, let’s make the DragonTurtle class again and provide its own implementation.

As you can see, the DragonTurtle class’ implementation overrides the Monster class’. This allows for more flexibility and more specified implementations.

Why is this important?

Well, for one, you don’t need to come up with unique names for every single method. Nor do you have to make long, complex names to differentiate between the methods. This is extremely important when you consider that programmers often think similarly. What if two programmers made a method called mean(). The only way they differ is that operates ONLY on arrays and the other works on Pandas Series. You call the methods in the exact same way. Without polymorphism, you would get an error because the programmers HAPPENED to name their functions the same, even though they operate differently. Thanks to polymorphism, however, we don’t have to worry about that.

Dynamic Polymorphism (in Other Languages)

It’s generally accepted that Dynamic Polymorphism is when a child object is assigned to a parent-type reference variable, but exhibit the behaviors of the child class’ methods. In essence, the object will LOOK like the parent class, but when you call one of it’s methods, it ACT like the child class. If you assign a parent object to a child-type reference variable and achieve the opposite: it will LOOK like the child class and ACT like the parent class. The same thing can be achieved in Python, but it looks a little different.

In this case, the object LOOKS like an Animal, but ACTS like a Fox. The way we accomplished this was essentially by overriding the object’s speak method so that it calls the fox class’ implementation. This is different from languages like Java where you would declare the reference variable as an Animal type and assign it to an Fox object. The reason Python is different is because all variables are dynamic; they are not given a “type” in the way they are in Java.

That’s probably more confusing than it needs to be. Just remember that Dynamic Polymorphism allows you to change which implementation of a method to that of another class’ for further customization.

Static Polymorphism

Static Polymorphism allows a class to have multiple methods of the same name as long as they take different arguments. This is called Method Overloading.

Here, we’ve provided two methods with the name of add which take in different arguments. One explicitly takes in integers. The other implicitly takes in floats (implied by the round function). Despite having the same name, Python doesn’t get confused about which method to use because the parameters differ in the datatypes they accept. When floats are passed, Python knows to execute the second method. If the arguments are integers, then it executes the first defined method.

Why is this important?

This makes the user experience much more streamlined. Imagine how annoying it would be if the print function had to be called differently based on the datatype of the object or variable you were referencing. Having to specify print_string, print_int, print_float, print_list, etc. each time would get VERY old, VERY quick!

Summary

There are many benefits to Polymorphism. Method names can be reused for a better user experience and a standardized interface across different classes. You can also control what methods are applied for more customized implementation.

Next week: Encapsulation!

--

--