Byte Introduction

Not having re-usable code is hurting the company by costing too much in code maintenance. Redesign the code base such that the developers can bring in their own nuances inspite of the re-use, using Polymorphism.

Skills:

OOPS

Objective

Learn polymorphism by applying it in a practical scenario.

Prerequisite

Taking up the OOP Essentials: Inheritance Byte is a prerequisite as the current Byte builds on top of the codebase used there.

Background

Polymorphism is about being able to change the functionality offered by a method, depending on the situation.


This is achieved in two ways

  1. Method Overriding - Child class implements a method that is already present in the parent class, resulting in the child method being invoked - Dynamic polymorphism.

  2. Method Overloading - Writing multiple methods with the same name but with different sets of parameters (or order of parameters) so that the appropriate method gets invoked based on the parameters passed - Static polymorphism. This may not be supported cleanly in some languages (e.g. Python).


In this Byte, we will learn method overriding and touch upon method overloading. We’ll see how they simplify code.

Primary goals

  1. Understand where and how to apply method overriding

  2. Understand where and how to apply method overloading

Objective

Learn polymorphism by applying it in a practical scenario.

Prerequisite

Taking up the OOP Essentials: Inheritance Byte is a prerequisite as the current Byte builds on top of the codebase used there.

Background

Polymorphism is about being able to change the functionality offered by a method, depending on the situation.


This is achieved in two ways

  1. Method Overriding - Child class implements a method that is already present in the parent class, resulting in the child method being invoked - Dynamic polymorphism.

  2. Method Overloading - Writing multiple methods with the same name but with different sets of parameters (or order of parameters) so that the appropriate method gets invoked based on the parameters passed - Static polymorphism. This may not be supported cleanly in some languages (e.g. Python).


In this Byte, we will learn method overriding and touch upon method overloading. We’ll see how they simplify code.

Primary goals

  1. Understand where and how to apply method overriding

  2. Understand where and how to apply method overloading

Current situation

The company wants to continue improving the Messaging Application that we started with in the OOPS Inheritance Byte.


The Messaging Application now has an Inheritance based design to support multiple Message Types. There is a base class for Message which is inherited by the child Message Type classes.

What the teams want

Each Message Type is now handled by different teams. The teams want more flexibility.


Requirement 1

They don’t want to use the functionality provided by some of the methods in the base class. The teams want to implement their own functionality for some of these methods. They also don’t want to rename the methods since that would mean changing all the client code that is presently invoking that method.

  1. Validation of the message contents

  2. Checking if the message has offensive content


Requirement 2

Each Message Type has a constructor that initializes their class variables to default values.

The teams want to have a way where some clients of this class can initialize the default values for some of these variables to a value of their choice.

Default way to achieve this

Obvious way to achieve the first requirement would be for the different teams to introduce new methods in the child class that they’re managing.

Yeah, something like


class TextMessage(Message):

    // other methods

    // …

    def is_valid_text_message(self):

        if (self.get_message_size() > 100):

            return True

        else:

            return False


class ImageMessage(Message):

    // other methods

    // …

    def isValidImageMessage(self):

        if (self.get_message_content() is None):

            return False

        else:

            return True


We can see that the criteria set by the TextMessage team is that the message size to be at most 100 for it to be considered a valid message. The ImageMessage team is more moderate about the condition for an image message to be valid & is only checking if the message has any content.


As the methods were implemented independently by different teams, we can see a difference in the method names used.


If we were to check the client side implementation for accommodating these, we’ll need something like the below method inside oop_way_with_polymorphism/android_client_handler.py


def check_message_for_validity(message: Message):

	if (message.get_message_type() is message.MSG_TYPE_TEXT):

		message.is_valid_text_message()

	elif (message.get_message_type() is message.MSG_TYPE_IMAGE):

		message.isValidImageMessage()

Drawbacks

  • Clients (e.g. android_client_handler.py) need to be aware of the method names used by each of the message types

  • Every time a new message type is introduced or teams want to have their own implementation, the Clients would need to make code changes.

Polymorphism with Message Override

A more OOPSie way to deal with the new requirement is to add a method with default implementation in the base class. That way, child classes can choose to use either the default method in the base class or "override" the original implementation in the base class with their own logic. The same method signature will be used by the child classes when they override.

What will the code look like?

This implementation is available inside the oop_way_with_polymorphism directory.


We can see the same requirement implemented by utilizing Polymorphism inside oop_way_with_polymorphism/message.py. Defined in it is the structure of is_valid() method. The method is to be used by the child classes to check if a message is valid. Individual message type classes will have to override this method as no default implementation is provided by the base class.


class Message:

    def is_valid(self):

        raise NotImplementedError;

The TextMessage & ImageMessage teams now can use the same method structure to implement their own logic independently.


Even if the is_valid() method had some default implementation in the base class, it could be overridden in the child classes.


class TextMessage(Message):

    // other methods

    // …

    def is_valid(self):

        if (self.get_message_size() > 100):

            return False

        return True


class ImageMessage(Message):

    // other methods

    // …

    def is_valid(self):

        if (self.get_message_content() is None):

            return False

        return True

As any child class of Message will be overriding the is_valid() method to implement their own logic for the message’s validity, clients of these classes need not worry. Even with any new message types, the child class would inherit from the base class and implement this method. So, no change/addition would be required on the client side.


def check_message_for_validity(message: Message):

    message.is_valid()


Advantages

  • Easy for each message type class to have its own methods override the default base class functionality.

  • Clients are not impacted

As always we can run oop_way_with_polymorphism/main.py to see a demo of our functionality.

image alt text

Oops!


We haven’t overridden the __str__() method, right? This gets called when we try to print() an object directly. Add your implementation of __str__() for at least TextMessage & ImageMessage to run the command without errors. The method should return a string.

Requirement

The second requirement is this.


Each Message Type has a constructor that initializes their class variables to default values.

The teams want to have a way where some clients of this class can initialize the default values for some of these variables to a value of their choice.

Default way to achieve this

The teams can set the values explicitly using the set methods after the object is created.


message_1 = TextMessage()

message_1.set_sender('DEFAULT_SENDER')

message_1.set_receiver('DEFAULT_RECEIVER')

Drawbacks

  • The values have to be set explicitly in every place that this object is being created.

Polymorphism with Message Overload

How about we utilize the constructor method which is already initializing the objects and pass these default values which we want to set for every object that is being created?


message_1 = TextMessage('DEFAULT_SENDER', 'DEFAULT_RECEIVER')

To handle this in the constructor of TextMessage and Message, we have to make these changes to both those constructors


class Message:

    def __init__(self, sender=None, receiver=None):

        # Common fields.

        self.__message_id = None

        self.__sender = sender

        self.__receiver = receiver

        self.__message_type = None

       # Other code not shown here


class TextMessage(Message):

    def __init__(self, sender=None, receiver=None):

        super().__init__(sender, receiver)

        self.__message_content = None

        self.__message_type = Message.MSG_TYPE_TEXT


Here, we have overloaded the__init__() methods to pass 2 new parameters. If those parameters are not passed, they will be initialized to None as seen in the parameter list passed to the__init__() method.


Note: This form of method overloading is restrictive in Python since if you have two methods with the same name, only the second method definition holds good. The first one cannot be used.


In method overloading in C++ or Java, the original method as well as the new overloaded method can be invoked by passing the different input parameters needed by each method.

Advantages

  • In a larger code base, the modification of a method by overloading in one place is preferred. For example, here, not adding those 2 additional lines to initialize the values every time is better. Also, it avoids two extra function calls.

  • Cleaner code.

Disadvantages

  • Too many overloaded methods may make the code harder to follow

Debrief

  • Practical Scenarios

    • An add() method could add integers or decimals or strings or two objects of a class.

    • A display() method could call different overloaded methods depending on the parameter passed.

    • Operator overloading in languages

    • Polymorphism via interfaces

  • Summary of Polymorphism

    • Method overriding builds on top of inheritance.

    • Rules for overriding are

      • The functions should have the same name

      • Parameters should be same in number and type

      • The classes should be different for the overriding functions

    • Method overloading can be done within any class.

    • Rules for overloading are

      • Function names should be the same

      • Parameters should be different in numbers or in types

      • Functions should belong to the same class

    • Why is it useful? - presents the ability to perform different actions using the same method signature, keeping code cleaner.

    • Where to apply? - when child classes need to implement their own logic for methods provided by the parent class. Or when a class needs related methods to have the same method name with only a different set of parameters.

    • How to apply? - override/overload methods in the parent class from the child class. Language specific constructs apply.

    • What is the drawback if we don’t use polymorphism? - maintaining code will be harder as some of the changes would require corresponding changes in all of the dependent/client code.

  • Types of Polymorphism - Types of Polymorphism

    • Compile-time polymorphism (Static)

    • Run-time polymorphism (Dynamic)

Curious Cats

  • Python + operator supports different types of objects as its operands. Is this an example of inheritance or polymorphism?

image alt text

  • When is the call to an overloaded & overridden method resolved in Java, at compile-time or run-time? What does this mean for overriding static methods?

  • Does method overriding bring with it, performance issues?

  • What is the deal with Message Overloading not working in Python? Can you try it out to prove whether it works or not, compared to Java/C++?

Newfound Superpowers

  • You’ve enriched your arsenal of OOPS concepts with Polymorphism - new Super Power unlocked - same identity different behaviour

image alt text

Now you can

  • Change behaviour of methods from parent class in the child class to suit the child class’ requirements

  • Use method overriding & overloading to implement polymorphism in your projects

  • Conquer these interview questions

    • What is Polymorphism?

    • How does method overriding & overloading differ?