What Is Polymorphism – Complete Guide

Welcome to the exciting world of polymorphism—a fundamental concept in programming that opens up a vast universe of possibility for code reusability and flexibility. Through polymorphism, developers can write more efficient and scalable programs. This tutorial is crafted to demystify this concept, making it accessible and engaging regardless of your experience level. Whether you’re new to coding or a seasoned developer, understanding polymorphism is essential in evolving your programming skills. Let’s embark on a journey to unlock the potential of polymorphism in your coding endeavors.

What Is Polymorphism?

The term polymorphism comes from the Greek words “poly,” meaning many, and “morph,” meaning form. In programming, polymorphism allows objects to be treated as instances of their parent class rather than their actual class. This enables a single function or method to process different types of objects.

What Is Polymorphism For?

Polymorphism is used for making software easier to extend and maintain. It allows programmers to create a consistent interface with underlying functionalities that can differ, thus you can write more generalized and reusable code. It plays a critical role in concepts like inheritance and interface design, making it a cornerstone of object-oriented programming.

Why Should I Learn Polymorphism?

Learning about polymorphism equips you with the ability to:

– Write more maintainable and readable code.
– Implement design patterns effectively.
– Make your programs dynamically adapt to changing requirements with ease.
– Enhance your problem-solving skills by approaching challenges from different angles within a unified framework.

Now that we’ve set the stage for polymorphism, in the next sections, we will delve into code examples that illustrate these principles in action, using game-like scenarios for a touch of fun and relatability.

CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Understanding Polymorphism in Practice

To truly grasp the power of polymorphism, let’s step into the realm of gaming, where we’ll create a simple class hierarchy related to game characters. We’ll start by defining a basic class and then extend it to illustrate polymorphic behavior.


# Base class
class GameCharacter:
    def attack(self):
        print("Character attacks!")

# Derived class
class Warrior(GameCharacter):
    def attack(self):
        print("Warrior slashes!")

# Derived class
class Archer(GameCharacter):
    def attack(self):
        print("Archer shoots an arrow!")

# Utilizing polymorphism
def character_attack(character):
    character.attack()

# Creating instances
warrior = Warrior()
archer = Archer()

# Calling the function with different objects
character_attack(warrior)  # Output: Warrior slashes!
character_attack(archer)   # Output: Archer shoots an arrow!

In the code above, both `Warrior` and `Archer` are derived from the `GameCharacter` class and provide their own implementation of the `attack` method. The function `character_attack` accepts any `GameCharacter` object and calls its `attack` method, which is determined at runtime. This showcases polymorphism, as the function can operate with objects of different classes.

Expanding Our Examples

Let’s expand our game universe by adding more character types and an example that utilizes a list of different characters. We’ll demonstrate how a single line of code can interact with a diverse set of objects thanks to polymorphism.


# Additional derived classes
class Wizard(GameCharacter):
    def attack(self):
        print("Wizard casts a spell!")

class Healer(GameCharacter):
    def attack(self):
        print("Healer uses a staff!")

# List of characters
characters = [Warrior(), Archer(), Wizard(), Healer()]

# Iterating over characters to attack
for character in characters:
    character.attack()

# Output:
# Warrior slashes!
# Archer shoots an arrow!
# Wizard casts a spell!
# Healer uses a staff!

Here, our list `characters` contains instances of various derived classes. As we iterate over the list and invoke the `attack` method on each character, polymorphism allows us to treat them all as `GameCharacter` objects without having to know their specific types.

Introducing More Complex Behavior

Beyond simple method overriding, polymorphism can be utilized to create complex systems. For instance, characters might have special attack modifiers or additional actions they can perform. We can provide default implementations in our base class and override them as needed.


# Base class with an additional method
class GameCharacter:
    def attack(self):
        print("Character attacks!")
    
    def special_move(self):
        print("Character performs a special move!")

# Derived class with an overridden special_move method
class Thief(GameCharacter):
    def special_move(self):
        print("Thief performs a sneak attack!")

# New thief instance
thief = Thief()

# Calling methods
thief.attack()            # Output: Character attacks!
thief.special_move()      # Output: Thief performs a sneak attack!

This Thief class introduces a new `special_move` method, showing that polymorphism goes beyond the basic usage of overriding just one method. It also helps you create a family of objects that have a common set of methods, which can be overridden as needed for each specific subclass.

Incorporating Interface Design with Polymorphism

We can also create a more formal structure that classes must adhere to by using interface design. This enforces that all subclasses following this interface must implement the specified methods. In dynamic languages like Python, this is less strict, but the concept still holds.


# Define an interface (Python doesn't enforce this strictly)
class IGameCharacter:
    def attack(self):
        pass  # Must be overridden

# Implementing the interface
class Knight(IGameCharacter):
    def attack(self):
        print("Knight wields a sword!")

# Implementing the interface
class Healer(IGameCharacter):
    def attack(self):
        print("Healer channels healing energy!")

# This enforces a clear contract that all game characters have an attack method
knight = Knight()
healer = Healer()

knight.attack()  # Output: Knight wields a sword!
healer.attack()  # Output: Healer channels healing energy!

By defining the `IGameCharacter` interface, we establish a contract that any class that claims to implement this interface must have their own implementation of the `attack` method. This uses polymorphism to ensure that there is a consistent way to interact with all game characters, emphasizing scalability and maintainability of the code.

These examples illustrate the fundamental aspects of polymorphism in an engaging context. Game development often requires the flexibility that polymorphism provides, streamlining the process of creating diverse behaviors for game entities. Understanding these principles is thus vital for any budding game developer or programmer looking to sharpen their skills.Polymorphism can be extended even further by incorporating abstract classes and methods, which can help to define a more formal structure for inheritance. In a language like Python, this is achieved with the `abc` module, which provides infrastructure for defining Abstract Base Classes (ABCs).


from abc import ABC, abstractmethod

# Define an abstract base class
class AbstractGameCharacter(ABC):
    @abstractmethod
    def attack(self):
        pass

# Derived class implementing the abstract method
class Rogue(AbstractGameCharacter):
    def attack(self):
        print("Rogue throws a dagger!")

# Trying to instantiate an abstract base class results in an error
try:
    character = AbstractGameCharacter()
except TypeError as error:
    print("Cannot instantiate an abstract base class:", error)

# Working with the concrete class
rogue = Rogue()
rogue.attack()  # Output: Rogue throws a dagger!

Abstract methods define a signature that all subclasses must provide. The abstract class itself cannot be instantiated—only classes that provide implementations for these abstract methods can be.

Moving forward, let’s explore a concept where polymorphism and composition work hand-in-hand. In game development, you often want to add behavior to your characters dynamically. This can be elegantly solved with the decorator pattern, which relies on polymorphism.


class MagicEnhanced(GameCharacter):
    def __init__(self, character):
        self.character = character
    
    def attack(self):
        print("Magic enhanced: ", end="")
        self.character.attack()

# Enhance a Warrior with Magic
magic_warrior = MagicEnhanced(Warrior())
magic_warrior.attack()  # Output: Magic enhanced: Warrior slashes!

In this example, `MagicEnhanced` is a class that accepts any `GameCharacter` and enhances its attack method. This implementation allows for dynamic behavior changes without modifying the original classes, displaying the flexibility of polymorphic design.

Let’s consider a scenario where we want to have a unified interface for handling game events, such as an attack or a heal action. We would create interfaces for each event and then implement them in relevant classes:


class IAttackable(ABC):
    @abstractmethod
    def attack(self):
        pass

class IHealable(ABC):
    @abstractmethod
    def heal(self):
        pass

class Paladin(GameCharacter, IAttackable, IHealable):
    def attack(self):
        print("Paladin swings a mighty hammer!")

    def heal(self):
        print("Paladin casts a healing spell!")

paladin = Paladin()
paladin.attack()  # Output: Paladin swings a mighty hammer!
paladin.heal()   # Output: Paladin casts a healing spell!

In this code snippet, `Paladin` derives from both the `GameCharacter` class and the `IAttackable` and `IHealable` interfaces. By combining these, we ensure that `Paladin` has both attack and healing capabilities, yet it’s all handled through a consistent interface.

Finally, polymorphism can be used to implement more advanced design patterns. For example, the strategy pattern uses polymorphism to allow the behavior of a class to be changed at runtime by substituting different strategy objects:


class AttackStrategy(ABC):
    @abstractmethod
    def execute(self):
        pass

class FireballStrategy(AttackStrategy):
    def execute(self):
        print("Casts a powerful fireball!")

class IceSpikeStrategy(AttackStrategy):
    def execute(self):
        print("Launches ice spikes!")

class Combatant:
    def __init__(self, strategy: AttackStrategy):
        self.strategy = strategy

    def change_strategy(self, strategy: AttackStrategy):
        self.strategy = strategy

    def perform_attack(self):
        self.strategy.execute()

combatant = Combatant(FireballStrategy())
combatant.perform_attack()  # Output: Casts a powerful fireball!

combatant.change_strategy(IceSpikeStrategy())
combatant.perform_attack()  # Output: Launches ice spikes!

Here, `Combatant` can change its attacking behavior dynamically by switching out the strategy object it uses. This is an excellent demonstration of polymorphism enabling the separations of concerns and enhancing the adaptability of code.

Through these examples, you can see how polymorphism is not just a theoretical concept; it’s a practical tool that can greatly improve the design and functionality of software. Whether in game development or any other field of coding, embracing polymorphism will help you create robust, extensible, and maintainable code structures. It is an invaluable technique that we highly encourage learners to master as part of their journey with us at Zenva.We can take polymorphism beyond game characters and into objects that the characters might interact with. Consider a game where characters can pick up different types of items, and each item has a use. We can implement a polymorphic ‘use’ method to handle this.


# Base item class
class GameItem(ABC):
    @abstractmethod
    def use(self):
        pass

# Derived potion class
class HealthPotion(GameItem):
    def use(self):
        print("You regain health!")

# Derived magical class
class MagicScroll(GameItem):
    def use(self):
        print("You learn a new spell!")

# Player interaction
def interact_with_item(item):
    item.use()

# Create items
potion = HealthPotion()
scroll = MagicScroll()

# Interact with items
interact_with_item(potion)  # Output: You regain health!
interact_with_item(scroll)  # Output: You learn a new spell!

Here, the `interact_with_item` function works with any `GameItem`, highlighting how polymorphism simplifies interactions with different object types without knowing their specifics.

Moving on, we can apply polymorphism to systems within the game world, such as combat systems, where different weapons have unique attack methods.


# Weapon base class
class Weapon(ABC):
    @abstractmethod
    def swing(self):
        pass

# Sword class
class Sword(Weapon):
    def swing(self):
        print("Sword slices through the air!")

# Axe class
class Axe(Weapon):
    def swing(self):
        print("Axe chops fiercely!")

# Using weapons
def attack_with_weapon(weapon):
    weapon.swing()

# Create weapon instances
sword = Sword()
axe = Axe()

# Execute attacks
attack_with_weapon(sword)  # Output: Sword slices through the air!
attack_with_weapon(axe)   # Output: Axe chops fiercely!

Each weapon class defines its own `swing` method. The `attack_with_weapon` function can invoke `swing` on any `Weapon` instance, allowing flexibility in the gameplay without coupling the code to specific weapon types.

A more intricate example might involve status effects that can affect characters differently. With polymorphism, we can manage these effects without creating a web of conditional statements.


# Base status effect class
class StatusEffect(ABC):
    @abstractmethod
    def apply(self, character):
        pass

# Poison effect class
class PoisonEffect(StatusEffect):
    def apply(self, character):
        print(f"{character} is poisoned!")

# Stun effect class
class StunEffect(StatusEffect):
    def apply(self, character):
        print(f"{character} is stunned!")

# Applying status effects
def inflict_status_effect(effect, character):
    effect.apply(character)

# Execute status effects
inflict_status_effect(PoisonEffect(), "Archer")  # Output: Archer is poisoned!
inflict_status_effect(StunEffect(), "Mage")      # Output: Mage is stunned!

This approach decouples the effects from the characters, adhering to the open/closed principle where classes are open for extension but closed for modification.

Additionally, we can utilize polymorphism to handle in-game events, like quests or dialogues, where each event type might require different handling logic.


# Event base class
class Event(ABC):
    @abstractmethod
    def trigger(self):
        pass

# Quest event class
class QuestEvent(Event):
    def trigger(self):
        print("A new quest has begun!")

# Dialogue event class
class DialogueEvent(Event):
    def trigger(self):
        print("A dialogue with the NPC starts!")

# Starting an event
def start_event(event):
    event.trigger()

# Create event instances
quest_event = QuestEvent()
dialogue_event = DialogueEvent()

# Trigger events
start_event(quest_event)       # Output: A new quest has begun!
start_event(dialogue_event)    # Output: A dialogue with the NPC starts!

Here, polymorphic `trigger` method calls enable game designers to add new types of events without altering existing code.

The beauty of polymorphism extends to its ability to handle various tasks such as rendering game elements. For example, each element might have a different rendering technique, but polymorphism allows us to handle rendering uniformly.


# Renderable base class
class Renderable(ABC):
    @abstractmethod
    def render(self):
        pass

class Character(Renderable):
    def render(self):
        print("Rendering the character")

class Environment(Renderable):
    def render(self):
        print("Rendering the environment")

# Render game elements
def render_elements(elements):
    for element in elements:
        element.render()

# Create renderable elements
character = Character()
environment = Environment()

# Render all
render_elements([character, environment])
# Output: Rendering the character
# Output: Rendering the environment

In this scenario, we consolidate rendering logic into a single function that handles any `Renderable` element, exemplifying polymorphism’s efficiency in a graphical context.

By embracing polymorphism, we can tackle complex scenarios while maintaining code simplicity and elegance. This principle encourages developers to think in an abstract and reusable way, vital for robust and scalable software design. Polymorphism isn’t just a topic to understand; it’s a practice to integrate into your day-to-day coding, and we at Zenva are committed to providing you with the knowledge and tools to do so seamlessly.

Where to Go Next?

Your journey through the world of programming, particularly with your newfound understanding of polymorphism, is just beginning. To continue expanding your skills and knowledge, we at Zenva encourage you to dive into our Python Mini-Degree. This comprehensive collection of courses will take you further into the versatile and in-demand world of Python programming. Whether you’re a beginner or an experienced coder looking to sharpen your expertise, our Python Mini-Degree provides you with the opportunity to learn Python by creating real-world applications, games, and much more.

In addition to the Python Mini-Degree, we offer an extensive array of programming courses to choose from, ensuring that no matter where your interests lie, you have access to the latest, industry-relevant content. Our courses are crafted to help you achieve tangible results, whether that’s launching your next game, developing innovative apps, or building a standout portfolio. At Zenva, we’re here to support you from the first line of code to the final semicolon in your journey from eager learner to seasoned professional.

Conclusion

As we’ve seen, polymorphism is more than a programming concept—it’s a gateway to writing dynamic, reusable, and efficient code. It enhances not only game development but any software project that benefits from flexible and maintainable code architecture. We at Zenva are excited to have guided you through this introduction to polymorphism, and we look forward to being a part of your ongoing journey in mastering programming skills. Delve deeper, solidify your understanding, and turn knowledge into practical achievement with our Python Mini-Degree.

Remember, the world of programming is vast and constantly evolving. By understanding foundational principles like polymorphism and continuously learning and practicing with Zenva, you equip yourself to meet the challenges of this dynamic field head-on. Keep coding, stay curious, and transform your passion into tangible skills that will shape your future. Happy coding!

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.