Mastering Inheritance in Object-Oriented Programming

Published Sept. 4, 2023, 6:36 p.m.

Agenda for Today's Lesson 📚

Introduction:

Today's lesson will focus on exploring the concept of inheritance in object-oriented programming. We'll delve into two essential relationships in OOP: the "Has-A" relationship, which involves objects working together, and the "IS-A" relationship, which defines class connections. Additionally, we'll compare "IS-A" and "HAS-A" relationships and discuss the choices for using composition vs. aggregation when building complex software systems.

🔹 Exploring Inheritance:

  • 🤝 Has-A Relationship: Objects working together
  • 👪 IS-A Relationship: Class connections
  • 🔄 IS-A vs HAS-A: Choosing the right link
  • 🎨 Composition vs Aggregation: Building big from small

Using Members of One Class Inside Another Class

In object-oriented programming, we can use the features of one class inside another class in different ways:

  1. By Composition (Has-A Relationship): This means that we create an object of one class inside another class. This way, we can use the data and functions of the first class in the second class. Composition (Has-A Relationship) helps us reuse code and avoid repetition.

    # Define the Engine class with two attributes 'a' and 'b' and a function 'm1'
    class Engine:
        a = 10  # Class attribute 'a' with value 10
    
        def __init__(self):
            self.b = 20  # Instance attribute 'b' with value 20
    
        def m1(self):
            print('Engine Specific Functionality')
    
    # Define the Car class
    class Car:
        def __init__(self):
            # Make an object of the Engine class inside the Car class
            self.engine = Engine()
    
        def m2(self):
            print('Car using Engine Class Functionality')
            # Use and print the attributes and call the function of the Engine class
            print(self.engine.a)
            print(self.engine.b)
            self.engine.m1()
    
    # Make an object of the Car class and show how it works
    c = Car()
    c.m2()
    

    Output:

    Car using Engine Class Functionality
    10
    20
    Engine Specific Functionality
    

    Explanation: In this example, we have an Engine class with two attributes a and b, and a function m1. The Car class makes an object of the Engine class, so it can use the data and functions of Engine.

  2. By Inheritance (IS-A Relationship): This means that one class inherits from another, creating a parent-child relationship. The child class can use the members of the parent class.

    # Define the parent class X with two attributes 'a' and 'b' and a function 'm1'
    class X:
        a = 10  # Class attribute 'a' with value 10
    
        def __init__(self):
            self.b = 20  # Instance attribute 'b' with value 20
    
        def m1(self):
            print("m1 function of X class")
    
    # Define the child class Y
    class Y:
        c = 30  # Class attribute 'c' with value 30
    
        def __init__(self):
            self.d = 40  # Instance attribute 'd' with value 40
    
        def m2(self):
            print("m2 function of Y class")
    
        def m3(self):
            # Make an object of class X inside class Y
            x1 = X()
            # Use and print the attributes and call the function of class X
            print(x1.a)
            print(x1.b)
            x1.m1()
            # Use the attributes of class Y and call the function 'm2' of class Y
            print(Y.c)
            print(self.d)
            self.m2()
            print("m3 function of Y class")
    
    # Make an object of class Y and show how it works
    y1 = Y()
    y1.m3()
    

    Output:

    10
    20
    m1 function of X class
    30
    40
    m2 function of Y class
    m3 function of Y class
    

    Explanation: In this example, we have a parent class X and a child class Y that inherits from X. This creates an IS-A relationship. The child class, Y, can use the data and functions of both X and Y.

IS-A vs HAS-A Relationship: How to Use and Extend Classes

In object-oriented programming, we often need to choose between IS-A (Inheritance) and HAS-A (Composition) relationships. These are two ways of using and extending classes. Let's see what they mean with a diagram and some code examples.

  • IS-A Relationship (Inheritance):
    • When we want to add new features to an existing class, we use IS-A relationships. In this relationship, one class is a child of another and inherits its attributes and methods.
    • Example: Employee class adds new features to Person class, meaning an Employee "is a" Person.
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def eat_and_drink(self):
        print('Eat Biryani and Drink Beer 🍽️🍻')

class Employee(Person):
    def __init__(self, name, age, emp_no, emp_salary, car):
        super().__init__(name, age)
        self.emp_no = emp_no
        self.emp_salary = emp_salary
        self.car = car

    def work(self):
        print("Coding Python is very easy, just like drinking Chilled Beer 🐍🍺")

    def emp_info(self):
        print("Employee Name:", self.name)
        print("Employee Age:", self.age)
        print("Employee Number:", self.emp_no)
        print("Employee Salary:", self.emp_salary)
        print("Employee Car Info:")
        self.car.get_info()

e = Employee('Durga', 48, 100, 10000, c)  # Creating an Employee object
e.eat_and_drink()
e.work()
e.emp_info()

Output:

Eat Biryani and Drink Beer 🍽️🍻
Coding Python is very easy, just like drinking Chilled Beer 🐍🍺
Employee Name: Durga
Employee Age: 48
Employee Number: 100
Employee Salary: 10000
Employee Car Info:
Car Name: Innova
Model: 2.5V
Color: Grey
  • HAS-A Relationship (Composition):
    • When we only want to use the features of an existing class without adding anything new, we use HAS-A relationships (Composition). In this relationship, one class contains an instance of another class, allowing it to access and use its members without inheritance.
    • Example: The Car class contains an instance of the Engine class, but it doesn't inherit from it; it just uses its features.
class Car:
    def __init__(self, name, model, color):
        self.name = name
        self.model = model
        self.color = color

    def get_info(self):
        print("\tCar Name: {}\n\tModel: {}\n\tColor: {}".format(self.name, self.model, self.color))

class Engine:
    a = 10

    def __init__(self):
        self.b = 20

    def m1(self):
        print('Engine Specific Functionality 🚗💨')

c = Car("Innova", "2.5V", "Grey")  # Creating a Car object
c.get_info()

Output:

    Car Name: Innova
    Model: 2.5V
    Color: Grey

This example shows the difference between IS-A (Inheritance) and HAS-A (Composition) relationships, where Employee IS-A Person and Car HAS-A Engine. 👏👏👏

Composition vs. Aggregation: How to Combine and Use Classes

In object-oriented programming, we often need to decide how to combine and use classes. There are two ways of doing this: Composition and Aggregation. Let's see what they mean with some examples and a mix of both. 🚀

Composition 🌟

Composition means a strong connection between two classes, where one class has the other as a part. In Composition, the part class cannot live without the whole class. It's like a "whole-part" connection.

🔍 Example - Composition:

class Brain:
    def __init__(self):
        print("Creating a Brain 🧠")

class Robot:
    def __init__(self):
        print("Creating a Robot 🤖")
        self.brain = Brain()

# Create a Robot object
r = Robot()

Output:

Creating a Robot 🤖
Creating a Brain 🧠

In this Composition example, a Robot "has-a" Brain. The Brain is made as part of the Robot, and the Robot cannot work without it.

Aggregation 🧩

Aggregation means a weak connection between two classes, where one class can live without the other. It's like a "whole-part" connection, but the parts can live on their own.

🔍 Example - Aggregation:

class Book:
    def __init__(self, title):
        self.title = title

class Library:
    def __init__(self, name):
        self.name = name
        self.books = []

    def add_book(self, book):
        self.books.append(book)

# Create a Library and Book objects
python = Book("Python for Beginners")
library = Library("City Library")
library.add_book(python)

Output:

No specific output in this case, as it's a structural example.

In this Aggregation example, a Library "has" Books. However, the Book objects can live independently of the Library.

Mixing Composition and Aggregation 🤝

In real-life situations, you can mix both Composition and Aggregation. For example, a Library may have Librarians (Aggregation) while each Librarian has a Badge (Composition).

Code Explanations:

  • Composition means making an instance of one class inside another, linking them closely.
  • Aggregation means having a reference to another class inside one class, allowing them to be more separate.
  • The mixed situation shows how different connections can work together in an application.

🚀 Have fun coding with Composition and Aggregation! 😄