How to solve problems using Inheritance and Polymorphism in Python

Inheritance and polymorphism in python

In this post I’m going to show you how to solve problems using inheritance and polymorphism in python, how to solve a real-life problem. I’ll keep working in the problem description I show you in the first post of the series. So, it is easy for you to understand the situation we have to solve.

This post is a part of a series of object-oriented programming in python.

If you missed the previous ones, this is the order you can follow:

  1. OOP in python: classes and objects.
  2. Python lists.
  3. Inheritance and polymorphism in python with examples.

Problem description

Let’s remember the problem description from the first post of the series.

Company “AutoCars” has a car rental service. The manager of the company is asking you to create software that helps him to improve the business. The company keeps a record of all the cars and the customers that rented a car at least one time.

For the cars, the business keeps in a record of the year model, make, type of the car (sedan, SUV, 4×4), price per day and if the car uses petrol or diesel.

For the customers, the company keeps the record of the name, id and preferred payment method (cash, ETF, card).

Your task is to design and implement a software in python that helps the manager to keep track of the cars and the customers.

The business started to grow while you were implementing the software that will help the manager. Now, several friends and family are renting cars in the company to support the manager. So, the manager wants to offer them discounts to thank them for their support. The manager is planning to give a 15% discount to friends and a 30% discount to family.

We can easily solve this issue by using programming, instead of leaving the manager to do it manually. Because that is what we do, we solve problems using programming.

Did you guess already how are we going to solve this?

Yes, you are right, we are going to use inheritance and polymorphism to give an elegant solution to this problem.

Solving the problem using Inheritance

Alert: the same problem always has several solutions in programming. I’m going to show you one solution, not “the solution”. Always keep your mind open when solving problems, that is the way you will find better solutions every time.

The first thing we have to do when we are solving a problem is to read several times until we fully understand what the problem is.

In this case, while reading we can see that now there are three types of customers. The three types are family, friends, and others.

When you see a situation like this, when you have several types of anything, it is the first indication that you can use inheritance to model the problem, and so the solution.

So, for any new type of customer, we create a new class. Then, we have the class FamilyCustomer and FriendCustomer.

Now we have to make the definitive test to know if there is inheritance or not. If you missed that explanation, you can read it here.

So, let’s write the questions:

  • Are all family customers, customers?
  • Are all fiend customers, customers?

You can easily see that the answer to the previous questions is yes in both cases. They are customers because they are paying for the service to support the manager.

We identified inheritance without even think about the attributes of each class. That is the right way. Inheritance in programming has nothing to do with the amount of (shared) attributes. Inheritance is about the semantic meaning of the relation; it is about the concepts. That is why the answer to the previous questions must be true. If the answer is false, then there is no inheritance, at least not in the order we made the question.

Because nothing changed about the “general customers”, we don’t have to change that class. This is one of the advantages of inheritance. We can extend our software solution with minor modifications to our previous code.

See below the implementation we made of the class Customer. It is here only to remind you about the code. I didn’t change anything there.

class Customer:
    def __init__(self, name, id, prefered_payment_method):
        self._name = name
        self._id = id
        self._prefered_payment_method = prefered_payment_method

    def get_id(self):
        return self._id

The solution: part I

Let’s see the implementations of the new classes.

class FamilyCustomer (Customer):
    pass

class FriendCustomer (Customer):
    pass

The keyword pass means we are not implementing anything in that class at this moment. Usually, that will mean that the class is a kind of a dummy class, it does nothing. However, because the two classes inherit from the class Customer, they have the same attributes and methods than the class Customer.

In other words, the two new classes have the same functionalities (and can be used for the same) than the class Customer.

Let’s now add a method to the class that represents the company, that helps the manager to know the price of the rent for a customer.

The solution: part II

def how_much_for_renting(self, car, customer, days):
    price = car.cost_if_renting(days)
    if (type(customer) == FamilyCustomer):
        return price - price * 0.3
    elif (type(customer) == FriendCustomer):
        return price - price * 0.15
    else:
        return price

What is new in this implementation?

The keyword “type” allows us to ask for the type of an object (or variable). The condition “type(customer) == FamilyCustomer” will return true if the type of the object customer is FamilyCustomer.

Let’s now test the new implementation with the following code.

car = Car("1", "2021", "BMW", "Sedan", 200, "Petrol")
days = 5
family_customer = FamilyCustomer("J", "idJ", "Cash")
friend_customer = FriendCustomer("K", "idK", "Cash")
normal_customer = Customer("NC", "idNC", "ETF")

print("Price of car1 for a family customer: ")
print(autocars.how_much_for_renting(car, family_customer, days))

print("Price of car1 for a friend customer: ")
print(autocars.how_much_for_renting(car, friend_customer, days))

print("Price of car1 for a normal customer: ")
print(autocars.how_much_for_renting(car, normal_customer, days))

In the code above, we have a car, a variable (days) that will be used as the number of days some want to rent the car, and three different types of customers. Notice that although the classes FamilyCustomer and FriendCustomer were not implemented, we can use them in the same way we use the class Customer. Again, that is one of the advantages of inheritance.

Let’s see the results after executing the code.

Price of car1 for a family customer: 
700.0
Price of car1 for a friend customer: 
850.0
Price of car1 for a normal customer: 
1000

As you can see, the price for the same car and the same number of days is different for the different types of customers.

At this moment, we can say we solved the new problem of the manager.

Compliments to you for solving another problem!!!

Solving the problem using Inheritance and Polymorphism

The manager wants to keep improving the business with us because of the solution we implemented. We made life easier for the manager.

Now the manager wants to send a personalised email to the customers wishing them a happy new year and informing them of a gift to enjoy during the first month of the year.

The email will have the following text for:

  • Family customers: “Dear family member, I wish you a happy new year.”
  • Friends’ customers: “Dear friend, I wish you a happy new year.”
  • Normal customers: “Esteemed <full name of the customer>, I wish you a happy new year.”

This is somehow similar that the previous problem we solved. We have to do something, depending on the type of customer. Let’s use the same approach that we used with the previous problem.

def text_for_happy_new_year_message(self, customer):
    if (type(customer) == FamilyCustomer):
        return "Dear family member, I wish you a happy new year."
    elif (type(customer) == FriendCustomer):
        return "Dear friend, I wish you a happy new year."
    else:
        return f"Esteemed {customer.full_name()}, I wish you a happy new year."

If we execute this code, the result is below.

Dear family member, I wish you a happy new year.

Dear friend, I wish you a happy new year.

Esteemed NC, I wish you a happy new year.

So, we solved the problem again. But let’s compare this solution with another one. It is always a good idea to know how to solve a problem in different ways.

If we see the messages, they start different depending to whom they are addressed. But after that, the message is the same.

Knowing this, we can associate the first part of the message with the specific type of the object, and the last part is the same for all the customers.

The solution

Let’s see how we can implement our solution using the previous fact.

class Customer:
    def __init__(self, name, id, prefered_payment_method):
        self._name = name
        self._id = id
        self._prefered_payment_method = prefered_payment_method

    def get_id(self):
        return self._id

    def full_name(self):
        return self._name
    
    def intro(self):
        return f"Esteemed {self._name}, "

class FamilyCustomer (Customer):
    def intro(self):
        return f"Dear family member, "

class FriendCustomer (Customer):
    def intro(self):
        return f"Dear friend, "

We added a method in each class named intro. This method is responsible to give the appropriate intro according to the type of customer.

After this, let’s see how the method that gets the whole message looks like.

def text_for_happy_new_year_message_v1(self, customer):
    return customer.intro() + "I wish you a happy new year."

Wait, what???

What happened here?

Here we just used polymorphism. The method intro will be executed according to the type of the object. That is the power of using inheritance and polymorphism to solve problems.

If the type is FamilyCustomer, the method intro that will be executed is the one implemented in the class FamilyCustomer. The same applies to the other two types of objects.

If you execute the same code from our first solution, using the new implementation, the result will be the same.

print(autocars.text_for_happy_new_year_message_v1(family_customer))

print(autocars.text_for_happy_new_year_message_v1(friend_customer))

print(autocars.text_for_happy_new_year_message_v1(normal_customer))

Result:

Dear family member, I wish you a happy new year.
Dear friend, I wish you a happy new year.
Esteemed NC, I wish you a happy new year.

So, what solution do you prefer?

Summary

Solving problems using Inheritance and polymorphism in python is a powerful tool that we have at our disposal.

Both of them are part of the most important and powerful tools for programmers that use the object-oriented programming paradigm.

Solving problems using polymorphism makes your code clearer and easy to maintain. Also, makes us use some of the principles of the object-oriented programming paradigm.

When implementing the method intro in each class, we are giving responsibility to each type of class. Each of them is responsible now to make a proper intro to a message address to a customer of that specific type. By doing that, the implementation related to getting the full message to the customer is way easier than before. The code is easier to understand and to maintain.

If tomorrow, the manager wants to change the introduction for the family, you only have to change in the FamilyCustomer class. Because is its responsibility to know how to introduce a text to that type of customer. There will be no need to change code in the class that represents the company, whenever we want to address the specific type of customer differently. This is a powerful tool we can use to create better software.

Don’t forget to subscribe to get more content like this one.

H@appy coding!!