ByteIntroduction

The company's messaging app needs to measure up to competition by adding support for voice messages and images. Upgrade the code to support this using inheritence.

Skills:

OOPS

Objective

Learn inheritance by applying it in a practical scenario.

Background

Inheritance is the concept of using the functionality offered by another class and building on top of it. This way, multiple new classes can build upon one class which is offering the base functionality, thus re-using code to avoid duplication.


In this Byte, we will see how Inheritance can be a good design principle and where to apply it.

Primary goals

  1. Understand how inheritance can lead to better software design

  2. Learn where it can be applied

Objective

Learn inheritance by applying it in a practical scenario.

Background

Inheritance is the concept of using the functionality offered by another class and building on top of it. This way, multiple new classes can build upon one class which is offering the base functionality, thus re-using code to avoid duplication.


In this Byte, we will see how Inheritance can be a good design principle and where to apply it.

Primary goals

  1. Understand how inheritance can lead to better software design

  2. Learn where it can be applied

Getting Started

You can download the source code from gitlab by executing one of the following commands:


git clone https://gitlab.crio.do/crio_bytes/me_inheritance.git

git clone git@gitlab.crio.do:crio_bytes/me_inheritance.git


If you don’t have Git already installed. Use this as a reference to help yourselves do that.


Execute this command in terminal to download the dependencies


pip3 install -r requirements.txt


Verify by running the single_class_proposal/main.py file


python3 single_class_proposal/main.py

image alt text

Company situation

Our company was one of the first to support Text Messaging through an App. However, in recent years, with the advent of more advanced Messaging apps like Whatsapp, Viber etc., it isn’t able to attract new users.


With plans to regain lost ground, the company wants to revamp its code and start adding support for other kinds of media.

Current Code Structure

Let’s look at the current code base. Look at the files in the outermost directory of the git repo you have cloned. The focus is on the Message class object, which is passed between multiple methods to make the messaging happen. The class contains all data fields that are relevant to the text message.


image alt text


The Message class is defined in message.py.

It has fields like __sender, __receiver & __message_content.

It has methods to access these data fields from outside the class. There are getters (methods to read value of fields) & setters (methods to write values of fields) for the above fields. This shows it has been designed with Encapsulation in mind.


Note: The field names with leading double underscores is how we say it’s restricted for direct access from outside the class. This is because Python doesn’t support "private" fields/methods by default.

image alt text

We have methods inside android_client_handler.py that implement the functionality to send messages making use of the Message class.

  1. send_message(message: Message) - method that wraps the other methods below to process & send the message. This will be invoked by the android client (simulated by main.py here).

  2. check_for_offensive_content(content: str) - method to check the message to be sent for offensive content and takes in as argument a string (str)

  3. store_message(message: Message) - method that stores the message info for later use and requires a Message object as argument

  4. deliver_message_to_receiver(message: Message) - method which handles the actual message sending to the receiver


Let’s do a simple demo of our app now by using the methods available. We have some code in main.py that does this


# create an object of the Message class

message_1 = Message()

# use the Message class setter to set the sender 

message_1.set_sender('SENDER_Amar')

# use the Message class setter to set the receiver

message_1.set_receiver('RECEIVER_Abhinaya')

# use the Message class setter to set the message content

message_1.set_message_content('Hello, Have you checked out https://blog.crio.do?')


# use method provided by android_client_handler.py to send the message

send_message(message_1)

Wanna try running main.py? Execute the below command


python3 main.py

Requirement

In order to start enhancing the App, the first set of requirements are listed below.

Add support for

  • Voice message

  • Image message


The fields and methods required for each Message Type is listed in the table below.


image alt text

Note: This doesn’t show the full list of messages. It shows a subset that is sufficient for the current topic.

Design Phase

The technical architect has now requested the team members to brainstorm and come up with design options and their drawbacks.

Proposal 1 - Add support for other message types in the existing Message class

One way to go about introducing new types of messages is to add more fields in our original Message class. How would we do that?


This implementation is available inside the single_class_proposal directory.

The new Message class implementation is provided in single_class_proposal/message.py. We can see that there are new fields added, specific to the text, voice and image message types in the Message class itself. There is a field to differentiate between the types as well.


MSG_TYPE_TEXT = 1

MSG_TYPE_VOICE = 2

MSG_TYPE_IMAGE = 3


# Common fields.

self.__message_type = None


# Fields related to text messages.

self.__text_message_content = None


# Fields related to voice messages.

self.__voice_message_content = None

self.__voice_duration_in_sec = None

self.__voice_quality_in_kbps = None


# Fields related to image messages.

self.__image_message_content = None

self.__image_resolution = None

self.__image_metadata = None

There are new getter & setter methods for dealing with the new message types as well. So, whenever we create objects of Message class for one type of message, all fields specific to the other message types will simply be untouched.


    def set_text_message_content(self, text_message_content: str):

        self.__message_type = Message.MSG_TYPE_TEXT

        self.__text_message_content = text_message_content


    def set_voice_message_content(self, voice_message_content: str,

                                  voice_duration: int, voice_quality: int):

        self.__message_type = Message.MSG_TYPE_VOICE

        self.__voice_message_content = voice_message_content

        self.__voice_duration_in_sec = voice_duration

        self.__voice_quality_in_kbps = voice_quality


    def set_image_message_content(self, image_message_content: bytes,

                                  image_resolution, image_metadata):

        self.__message_type = Message.MSG_TYPE_IMAGE

        self.__image_message_content = image_message_content

        self.__image_resolution = image_resolution

        self.__image_metadata = image_metadata


    def get_text_message_content(self):

        return self.__text_message_content


    def get_voice_message_content(self):

        return self.__voice_message_content


    def get_image_message_content(self):

        return self.__image_message_content


    def get_text_message_size(self):

        return len(self.__text_message_content)


    def get_voice_message_size(self):

        return len(self.__voice_message_content)


    def get_image_message_size(self):

        return len(self.__image_message_content)


If we check the single_class_proposal/android_client_handler.py, there are separate methods for processing & delivering the varied types of messages using methods provided by message.py. ( android_client_handler.py has changed from the previous form where only text messages were required. Compare them to see the differences)

Handling Text Messages


def send_text_message(message: Message):

    if (message.get_text_message_content() == ''):

        raise Exception('Cannot send empty string')


    check_for_offensive_content(message.get_text_message_content())

    store_message(message)

    deliver_text_message_to_receiver(message)


def check_for_offensive_content(content: str):

    if (content.find('abuse') != -1):

        raise Exception('Abusive language used; Discard')


def deliver_text_message_to_receiver(message: Message):

    if (message.get_text_message_size() > 100):

        raise Exception('Message too large to send {} > 100 bytes', message.get_message_size())

    print ('Message "{}" delivered successfully to {}'.format(message.get_text_message_content(),

                                                              message.get_receiver()))

    pass


Handling Image Messages


def send_image_message(message: Message):

    if (message.get_image_message_content() is None):

        raise Exception('Cannot send empty image')


    check_for_offensive_image_content(message.get_image_message_content())

    store_message(message)

    deliver_image_message_to_receiver(message)


def check_for_offensive_image_content(image_content: bytes):

    print ('Check for offensive image content')


def deliver_image_message_to_receiver(message: Message):

    print ('Image message delivered successfully to {}'.format(message.get_receiver()))

    print ('Feel free to write code that will render the image,'

           ' for now we will print the bytes')

    print ('Image bytes: {}', message.get_image_message_content())

    pass

For the most part, single_class_proposal/main.py is similar to the earlier main.py. The earlier message methods are now renamed with text_message methods and an example of image message has also been added.


Execute this and see the output


python3 single_class_proposal/main.py

Drawbacks

  • The Message class contains elements and methods related to all 3 message types, but only some of them are required by the clients based on the message type. Even if a client (e.g. android_client_handler.py) wants only a text message, they will get all other fields in the Message object which they wouldn’t use.

  • Extending this code with more message types will add more elements and methods in the Message class, making it difficult to maintain.

Proposal 2 - Create each message type as a separate class which operates independently

Another manner to go about supporting new types of messages will be to create different classes for each. Each one takes care of a single message type. Yeah! Something like carbon-copies of our Message class. Each of these classes will have all the common fields & their methods as well as fields & methods specific to that message type. As the classes will only be having fields & methods they absolutely require, there won’t be any unused functionality.


Note: No code has been provided for this proposal. You should implement this proposal and compare the files to see the differences.

Drawbacks

  • Lot of repeated code across these classes. Change needed in one of the common fields or methods would mean modifying all these classes. Low maintainability and low extensibility.

  • To add support for a new message type, we would add another new class which is independent, but with repeated fields and methods.


However, the tech lead is not fully satisfied with these proposals. He thinks they are not easily maintainable. Something is missing here and they can do better.

Is this situation suitable to bring in Inheritance?

Yes, this situation is just right for Inheritance.


The definition of inheritance is that the parent/base class holds the common fields and methods, while the child classes get to inherit the parent’s fields and methods and add their own on top of it.

That way each child class (representing one message type) remains independent of other child classes.

What does the Inheritance based design look like?

This implementation is available inside the oop_way directory.

Inside oop_way/message.py, we use a Message parent class to define fields & their methods which are common to all types of messages. E.g. message id, sender, receiver etc.

For each of the message types, namely, Text, Image & Voice, we create new children classes TextMessage, ImageMessage & VoiceMessage. These children classes inherit the Message class and hence need only add any fields and methods specific to them.

image alt text

Advantages

  • Code reuse for common fields and methods (by inheriting parent class) as opposed to repeated code in the earlier Proposal 2.

  • Clean class structure for the child classes that are easily maintainable. Each message type can be modified without impacting other classes since it holds fields/methods specific to itself.

  • The Clients get only the fields and methods for the message type they are interested in by creating an object of the child class. This is in contrast to getting all fields by creating an object of a large single Message class, as was the case with Proposal 1 earlier.


Execute this and see the output


python3 oop_way/main.py

Additionally

Pay close attention to these lines of code.


single_class_proposal/android_client_handler.py


def send_text_message(message: Message):

def send_image_message(message: Message):


oop_way/android_client_handler.py


def send_text_message(message: TextMessage):

def send_image_message(message: ImageMessage):

Note that with the Inheritance based design, the invocation of the methods need to know which exact message type to use. This is not very good.


Inheritance has helped clean up the structure from the service provider side/implementation side (message.py). We’ll see how this design can be further improved to help the caller/client side (android_client_handler.py) when we visit Polymorphism.


Additional Enhancement

A new requirement has come in to add support for

  • Video messages

How easy or difficult is it to implement this with the Inheritance based design?

Debrief

  • Summary of Inheritance

    • Why is it useful? - It helps separate out common functionality and fields so that they can be reused across many other classes cleanly. Avoids code redundancy.

    • Where to apply? - In the code base when different objects need to share common fields and functionality.

    • How to apply? - Define a parent class with all shared functionalities & child classes that inherit these from the parent class

    • What is the drawback if we don’t use inheritance? - Code duplication, Higher maintenance cost.

  • Types of Inheritance - Types of Inheritance

    • Single inheritance

    • Multiple inheritance

    • Hierarchical inheritance

    • Multilevel inheritance

    • Hybrid inheritance

  • Practical Scenarios

    • In Java, modules extend the Standard Java Exception class to include additional functionality or information that the standard class does not provide.

    • Real world C++ examples

    • Java libraries Set and List extend Collection class. Vector and ArrayList extend AbstractList which in turn extends AbstractCollection

  • General rules of Inheritance

    • A class (child class) can extend another class (parent class) by inheriting its features.

    • Implements the DRY (Don’t Repeat Yourself) programming principle.

    • Improves code reusability.

  • Inheritance vs Composition

    • Composition is another way to reuse code from another class. It is achieved by creating a class containing instances of other classes that implement the desired functionality, rather than inheritance from a base or parent class.
  • Encapsulation and Abstraction vs Inheritance

    • Encapsulation and Abstraction are helpful to develop and maintain a big codebase.

    • When there are similar objects in this big codebase that share common functionality, the common functionality and fields can be separated out into a separate base class which is then inherited by child classes.

Curious Cats

  • Does Python support multiple inheritance? How about Java?

  • What is the C++ equivalent of the Java interface?

  • Come up with a practical scenario where Composition is better than Inheritance.

Newfound Superpowers

  • You’ve inherited the idea of Inheritance and you are now the owner of it!

Now you can

  • Reduce code duplication by utilizing inheritance in your projects, when you see common functionality is needed.

  • Answer these interview questions

    • What is Inheritance?

    • When should you use Inheritance?

    • Inheritance vs Composition