inheritance_and_composition

Inheritance and Composition: A Python OOP Guide.

A python class is an object constructor or a “blueprint” for creating objects.

Classes are written to organize and structure code into meaningful blocks.

WHAT YOU LEARN IN THIS TUTORIAL

  • How to build a relationship between classes using inheritance and composition.
  • When to favor composition over inheritance.

 

Inheritance

Inheritance allows us to define a class that inherits all the methods and properties of another class.

  • The parent class is the class being inherited from, also called a base class.
  • The child class is the class that inherits from another class, also called derived class.

Create a Parent class

class Person:
    def __init__(self, fname, lname):
       self.firstname = fname
       self.lastname = lname

    def greet_user(self):
       print(f" Hello {self.firstname} !")

Create a Child  class

 class User(Person):
     pass
new_user = User("Mike", "Dovey")
new_user.greet_user()    # Hello Mike !

You see that the User class(child class) has inherited the attributes of the parent class (firstname and lastname) and also the method (greet_user).

Abstract base classes

Are classes that can be inherited but never instantiated. Python provides the abc module to define abstract base classes.

Why would we want to do this? Sometimes we want to create a class that serves as a template for suitable objects by defining a list of methods and properties that these objects must implement.

from abc import ABC, abstractmethod

class User(ABC):
    def __init__(self, id, name):
        self.id = id
        self.name = name

    @abstractmethod
    def say_hello(self):
        print("Hi")

Try to instantiate it new_user = User(), and see the error message

TypeError: Can't instantiate abstract class User with abstract methods say_hello

Try inheriting it

class Student(User):
    
    #let us override the say_hello method of base class
    def say_hello(self):
        print(f"Hi {self.name}")
    
x = Student(1, "Dave")
x.say_hello()    #Hi Dave

In this section, we’ll design a

  • A simple music app

A music collection that allows artiste to save their tracks and albums.

  • A Simple school system.

A  school portal that registers a lecturer, school admin, and student and assigns each of them a different role.

These examples will demonstrate the simple use of inheritance.

class Song:

    def __init__(self, id,song_title, genre,release_year):
        self.id =id
        self.song_title = song_title
        self.genre= genre
        self.release_year = release_year
       
        

class Artist(Song):
    def __init__(self, name,*args,**kwargs):
        self.name = name
        self.song_track ={}
        self.album = []
        super().__init__(*args, **kwargs)
        
    def create_song(self):
        self.song_track[self.id] = [self.song_title,self.genre,   
                                         self.release_year]
        return self.song_track

nickiminaj= Artist("Nicki Minaj", 1, "Right Thru Me", "Rap" ,"2010")
print(nickiminaj.create_song())
//code output

{1: ['Right Thru Me', 'Rap', '2010']}

Our class Song is the base class for Artist because every Artist has a song title, release year for a new song, and genre the music belongs to.

We create an Artist subclass to create a song.

The __init__method of the base class initializes all the instance variables that are common to all subclasses. 

In each subclass, we override the __init__method so that we can use it to initialize that class’s attributes – but we want the parent class’s attributes to be initialized as well, so we need to call the super() function that will make the child class inherit all the methods and properties from its parent.

 A common convention is to add the specific parameters for each successive subclass to the beginning of the parameter list and define all the other parameters using *args and **kwargsfor the parent’s base class parameters because the subclass doesn’t need to know the details about the parent class’s parameters. 

/* A simple school system*/
class Person:
    def __init__(self, id, username, fullname):
        self.id = id
        self.username = username
        self.fullname = fullname
        

class Student(Person):
    UNDERGRADUATE, POSTGRADUATE, MASTERSTUDENT = range(3)

    def __init__(self, student_type, course, *args, **kwargs):
        self.student_type = student_type
        self.course = course
        super().__init__(*args, **kwargs)

    def enrol(self):
        return f"{self.fullname} just enrolled in {self.course}"


class StaffMember(Person):
    PERMANENT, TEMPORARY = range(2)

    def __init__(self, employment_type, *args, **kwargs):
        self.employment_type = employment_type
        super().__init__(*args, **kwargs)


class Lecturer(StaffMember):
    def __init__(self, course,*args, **kwargs):
        self.course = course
        super().__init__(*args, **kwargs)

    def assign_teaching(self):
        return f"{self.fullname} teaches {self.course}"

jane = Student(Student.POSTGRADUATE,"English language", 1, "jane", "Jane Smith")
print(jane.enrol())

bob = Lecturer("Sociology", StaffMember.PERMANENT, 1, "bob_smith","Prof.Bob Smith")
print(bob.assign_teaching())
Jane Smith just enrolled in English language
Prof.Bob Smith teaches Sociology

Our base class is Person, which represents any person associated with a university. We create a subclass to represent students and one to represent staff members, and then a subclass of StaffMember for people who teach courses (as opposed to staff members who have administrative positions.)

We use different attributes for the kind of student (undergraduate or postgraduate) that someone is and whether a staff member is a permanent or a temporary employee because these are different sets of options.

We have also added a method to Student for enrolling a student in a course, and a method to Lecturer for assigning a course to be taught by a lecturer.

multiple inheritances

In Python, a class can inherit from multiple other classes, unlike single inheritance that takes only one class.

SINGLE CLASS INHERITANCE:

class ParentClass:
    pass

class Child(ParentClass):
    pass

MULTIPLE INHERITANCES

class Base1:
    pass

class Base2:
    pass

class Base3: 
    pass

class MultiSubclass(Base1, Base2):
    pass

class MultiChildclass(Base1, Base2,Base3): 
    pass

When working with multiple inheritances, it is always a good idea to design a class that is not intended to stand alone but exists to an extra functionality to another class.

A Mixin is a class that is not intended to stand on its own, it exists to add extra functionality to another class through multiple inheritances.

In this section, we’ll

  • Explore the implementation of Django class views

Using mixins with Django class-based views.

We want to write a class-based view that responds only to POST,

  • we’ll subclass View(parent view that creates Django views)
  • write a post() method in the subclass.
  • However if we want our processing to work on a particular object, identified from the URL, we’ll want the functionality provided by SingleObjectMixin( a class that provides a mechanism for looking up an object associated with the current HTTP request).
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.views import View
from django.views.generic.detail import SingleObjectMixin
from books.models import Author

class RecordInterest(SingleObjectMixin, View):
    """Records the current user's interest in an author."""
    model = Author

    def post(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return HttpResponseForbidden()

        # Look up the author we're interested in.
        self.object = self.get_object()
        # Actually record interest somehow here!

        return HttpResponseRedirect(reverse('author-detail', kwargs={'pk': self.object.pk}))

 

COMPOSITION

In composition, we do not inherit from a base class but we establish relationships between classes through the use of instance variables.

In composition, one of the class is composed of one or more instances of the class.

In composition, a class known as composite contains an object of another class known to as component. In other words, a composite class has a component of another class.

class ClassA:
    pass

class ClassB:
    x = ClassA()

From the code snippet above you can see that ClassBis a composite and ClassAis a component because ClassB contains an object of classA.

In this section, we’ll continue with

  • The Simple school system.

We would extend the school system we had designed before, we design courses a student can enroll in and a lecturer is assigned to teach and departments the student or lecturer belongs to respectively.

I would create a new python module to handle the department and course creation in the school system.

course.py


class Course:
    def __init__(self, course_title,course_code):
        self.course_title = course_title
        self.course_code = course_code
        
    def register_course(self):
        return f"{self.course_title} with {self.course_code}"

   def update_course(self): 
        pass
    
class Department:
    def __init__(self, faculty,department):
        self.faculty = faculty
        self.department = department
        
    def register_dept(self):
        return f"Department of {self.department}| Faculty of {self.faculty}"

   def update_dept(self):
        pass

 

from course import Course, Department

class Person:
    def __init__(self, username, fullname):
        self.id = id
        self.username = username
        self.fullname = fullname

class StaffMember(Person): 
    PERMANENT, TEMPORARY = range(2) 
    def __init__(self, employment_type, *args, **kwargs): 
        self.employment_type = employment_type 
        super().__init__(*args, **kwargs) 
        
           
class Student(Person):
    UNDERGRADUATE, POSTGRADUATE, MASTERSTUDENT = range(3)

    def __init__(self, student_type, course_title, course_code, *args, 
                       **kwargs):
        self.student_type = student_type
        self.course_title = course_title
        self.course_code = course_code
        super().__init__(*args, **kwargs)
        #instantiating the base
        self.course= Course(self.course_title,self.course_code) 
        

    def enrol(self):
        return f"{self.fullname} just enrolled in 
                {self.course.register_course()}"


class Lecturer(StaffMember):
    def __init__(self, course, department, faculty,*args, **kwargs):
        self.course = course
        self.faculty = faculty
        self.department = department
        super().__init__(*args, **kwargs)
        #instantiating the base
        self.position = Department(self.department,self.faculty)
        

    def assign_teaching(self):
        return f"{self.fullname} teaches {self.course}"
    def assign_department(self):
        return f"{self.fullname} is a lecturer in    
                   {self.position.register_dept()}"

    
    
    

jane = Student(Student.POSTGRADUATE,"English language", "ENG_101", "jane", "Jane Smith")
print(jane.enrol())

bob = Lecturer("Observing society", "Sociology","Social 
                 Sciences",StaffMember.PERMANENT, 
                 "bob_smith","Prof.Bob Smith")
print(bob.assign_teaching())
print(bob.assign_department())

print("============================")
#create new departments and register new courses
new_course = Course("Introduction to Organic Chemistry", "CHEM_101")
print(new_course.register_course())

new_dept = Department("Pure & Industrial Chemistry","Physical Sciences")
print(new_dept.register_dept())

//code output
Jane Smith just enrolled in English language with ENG_101
Prof.Bob Smith teaches Observing society
Prof.Bob Smith is a lecturer in  Department of Social Sciences| Faculty of Sociology
============================
Introduction to Organic Chemistry with CHEM_101
Department of Physical Sciences| Faculty of Pure & Industrial Chemistry

The course.py file handles creating new and updating courses and departments in the school can be used in different situations.

You notice that the Student doesn’t inherit the Course interface but it is only leveraging its implementation.
This is more flexible than inheritance because it models a loosely coupled relationship. Changes to the Course class have minimal or no effects on the Student class.

Same with the Lecturer class, it doesn’t inherit the Course and Department and there is no complexity, just imagine, the Lecturer class inheriting both the interface and implementation., it would like something like this

class Lecturer(StaffMember, Department,Course):  
    pass

this would have been very complex because you may need to know the different class hierarchy and override some of the base class methods.

 

When to Use Inheritance or Composition

The question of inheritance versus composition comes down to an attempt to solve the problems stated below:

  • Don’t Repeat Yourself(DRY
  • Write Everything Twice (WET)

because you don’t want to have repeated code that is not reusable and difficult to maintain.

 Inheritance solves this problem by creating an interface for you to inherit properties and methods in your base class.

Composition solves this by giving you modules and the capacity to call functions in other classes.

If both solutions solve the problem of reuse, then which one is suitable?

The answer is highly subjective, but I’ll give you my answers.

1. Avoid multiple inheritances at all costs, as it’s too complex to be reliable. If you have no option than that, then be prepared to know the class hierarchy and spend time finding where everything is coming from.

2. Use composition to package code into modules that are used in many different unrelated places and situations.

3. Use inheritance over composition in Python to leverage both the interface and implementation of the base class.

 

credits.
  • Confluence, her blog inspired some of my code snippets examples.
  • RealPython
  • Learn Python3 the hard way – Zed

 

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *