4-3 Inheritance and Polymorphism

You and I are both “person objects” in our world.  Every person has a name and an age and similar behaviours like eating and sleeping.  However, not every person is the same.  For example, you are a more specific kind of person called a “student” and I am a “teacher”.  We have similar characteristics and behaviours that make us people, but we are also different.  For example, as a student, you have a student number and behaviour called studying that are both unique to students.   I have an employee number and a different behaviour called lecturing.

When a class is a specialized version of another class, we can say that there is an “is a” relationship between them.   For example, a student “is a” person.  When an “is a” relationship exists between two classes, it means that the specialized class has all of the characteristics of the general class, plus additional characteristics that make it unique.

Inheritance in Python

In Object-Oriented Programming, the concept of inheritance is used to create an “is a” relationship between classes.  Inheritance allows you to extend the capabilities of a class by defining another class that is a specialized version of the general one.   We call the general class, the superclass (sometimes called the parent class), and the specialized class the subclass (sometimes called the child class).

You can think of a subclass as an extended version of its superclass.  The subclass inherits instance variables and methods from its superclass without any code having to be rewritten.  Moreover, new instance variables and methods may be added to the subclass to make it a specialized version of the superclass.

Let’s see how inheritance works in Python.  Suppose we are developing a game with different types of monsters: ghosts, vampires, etc…  To do this we will create one (general) superclass called Monster, and then subclasses called Ghost, Vampire, and so on.

The Monster Class

Regardless of the type, all monsters must have a name.  Because name is a general characteristic of all types of monsters, we will put this attribute and any related methods into our Monster class:

If you are following along in Python, save the code above in a module called myMonsters.py.

Right away, on line 1, you will notice something we haven’t seen yet: ( object ) after the class name.   In Python, all classes inherit from a superclass called object, unless you state otherwise.   If you don’t specify, Python automatically assumes ( object ) should be there.  This is optional for our Monster and Coin classes since they are not inheriting from a specific parent class.

The __init__() method accepts monsterName as a parameter and calls upon the setName() mutator method to set the value of the name instance variable for Monster objects.   Notice that since I already wrote “sanity checking” code in the setName()method I reused it by calling this mutator within the __init__() method.  I’ve also included an accessor method for the nameattribute, and a method called speak() that prints a greeting message.

So far so good, nothing terribly new yet.  Our Monster class is a complete class that we can create objects from.  Let’s perform some basic testing of this class using the Python shell:

>>> import myMonsters
>>> mrRao = myMonsters.Monster(“Mr. Rao”)
>>> mrRao.speak()
GGrrr.. I am a ‘Mr. Rao’ Monster object.
>>> mrRao.getName()
‘Mr. Rao’
>>> mrRao.setName(“Mr. Lee”)
>>> mrRao.speak()
GGrrr.. I am a ‘Mr. Lee’ Monster object.

The Ghost Class

Now let’s create a subclass called Ghost that inherits from the Monster superclass.  Add the following Ghost class code to the myMonsters.py module.  A ghost “is a” monster, so it will have a name and be able to speak() (like all monsters) but we will add a unique instance variable called isVisible to keep track of whether it can be seen or not.  This makes a Ghost a specialized kind of Monster.  Here’s the Ghost class code:

Line 1 names our Ghost class and says that we want to base our new class on the Monster class.  In other words, (Monster) after the Ghost class name indicates that we are inheriting any instance variables (in this case only name) and any methods from the Monster class.   Once we have inherited from Monster, we add the instance variables and methods that make a ghost a specialized kind of monster.

The __init__() method includes the self parameter as usual but also has parameters called name and visible.  This makes sense because a Ghost object will have a name (like any monster) but also an instance variable to keep track of whether it isVisible or not (unique to ghosts).  The Monster class, however, provides the name attribute so on line 7 we call the Monster class’s __init__() method to set the name for us.   This is an example of reusing code from a superclass.

On line 8, the isVisible instance variable is initialized with the value passed using the visible parameter.   As you would expect, I’ve included an accessor and mutator method for the isVisible instance variable to provide access to it.

Now let’s test our Ghost class in the Python shell:

>>> import myMonsters
>>> mrWoo = myMonsters.Ghost(“Mr. Woo”, True)
>>> mrWoo.getIsVisible()
True
>>> mrWoo.setIsVisible(False)
>>> mrWoo.getIsVisible()
False

But we can do more than this!  Because a ghost “is a” monster, we have inherited the methods from the Monster class into our Ghost class.  This means that we can call Monster methods as if they were part of the Ghost class:

>>> mrWoo.getName()
‘Mr. Woo’
>>> mrWoo.setName(“”)
>>> mrWoo.speak()
WOooo.. I am a Ghost named Unknown.

Even though we did not define a getName() or setName() method in our Ghost class, it inherits these from the Monster class!   Inheritance is one of the most powerful features of object-oriented programming because it allows us to reuse and extend code from other classes without having to type it all out again.   This kind of code reuse is very important when we get into game development because it will save us a lot of work.

Inheritance in UML

Here is a UML diagram showing the relationship between our Monster and Ghost classes.   You show inheritance in a UML diagram by drawing a line with an arrowhead pointing from the subclass to the superclass.   Notice that we don’t list methods inherited from the superclass (Monster) in its subclass (Ghost), the arrow makes this redundant.

Also, notice that the Ghost class is supposed to inherit the speak() method from Monster but actually provides its own version of the speak() method.   Have a look back at the Ghost class code if you did not notice this before.

When a child class is supposed to inherit a method from its parent, but then provides its own method with exactly the same name and parameters this is known as method overriding.   Another way to think about this is to imagine that you have inherited a facial feature from one of your parents that you would rather not have.  No problem!   Through the miracle of modern plastic surgery, you can replace (override) that feature with one of your own design!

Polymorphism

Method overriding is related to one last important feature of OOP called polymorphism.   This is a very impressive sounding word but it really means two simple things in OOP: (1) method overriding allows a subclass to replace a method that would have been inherited from a superclass, and (2) when you call the method that exists in both the parent and child class the Python interpreter will automatically call the correct version you need depending on the type of object used to call it.

Look back to our testing in the Python shell — where we instantiated a Monster object (referred to by the variable mrRao) and then typed mrRao.speak().  In this case, the speak() method from the Monster class was run.  And, when we instantiated a Ghostobject (referred to by the variable mrWoo) and then typed mrWoo.speak(), the speak() method from the Ghost class was run. Python called the version of the method that made the most sense based on the object used to call the method.

The Three Pillars of OOP

Believe it or not, we’ve covered the three most important concepts in Object-Oriented Programming!  Let’s recap:

    1. Encapsulation: Combining instance variables and related methods to manipulate those variables into one unit called a class.
    2. Inheritance: When a child class inherits instance variables and methods from a parent class and adds unique instance variables and methods so that it is more specialized than its parent.
    3. Polymorphism: When a child class overrides a method from its parent and Python knows the correct version of the method to call based on the type of object.

Having a good understanding of this will be very helpful as we learn how to work with the classes provided by the pygame module.

You Try!

  1. Start a new page in your Learning Journal titled “4-3 Inheritance and Polymorphism”.  Carefully read the notes above and in your own words summarize the key ideas from each section.
  2. Which is more specialized, a superclass or subclass?  Explain.
  3. What does it mean to say there is an “is a” relationship between two classes?  What principle of OOP is related to this?
  4. What does a subclass inherit from its superclass?
  5. What does it mean if a subclass overrides a method inherited from a superclass?  What principle of OOP is related to this?
  6. Be creative! Design a class that inherits from the Ghost class.  Your class should represent a specialized kind of ghost and include at least one unique instance variable, accessor/mutator methods, and one other method for your subclass.   By inheriting from the Ghost class, your new class will have all of the instance variables and methods from the Ghost class and the Monster class! Write a program to demonstrate all of the methods unique to your new class and all those it has inherited.