, ,

Composition Vs Inheritance in Python – Object Oriented Programming – Understand in Depth

Composition and inheritance they are two fundamental concepts in OOP that help developers organize and structure their code. While they serve similar purposes, they differ in how they achieve code reuse and flexibility. Let’s explore these concepts in depth, understand their differences, and see some examples to clarify their use.

Composition
Composition is a design technique in which a class contains an object of another class, allowing it to use the functionality of that class. In other words, composition enables creating complex objects by combining simpler ones. The main benefit of composition is that it promotes code reuse without the tight coupling often associated with inheritance.

Example 1: Car and Engine
Consider a Car class that has an Engine class as a component:

class Engine:
    def start(self):
        print("Engine starting")


class Car:
    def __init__(self):
        self.engine = Engine()

    def start(self):
        print("Car starting")
        self.engine.start()

# Usage
my_car = Car()
my_car.start()

In the above example, Car uses Engine functionality without inheriting from Engine. This allows for flexibility and easier maintenance.

Explanation of the above code:

In this code, we have two classes: Engine and Car. The Engine class has a method start that prints “Engine starting”. The Car class has an init method (which is a special method used for initialization) that creates an instance of the Engine class and assigns it to the engine attribute of the Car instance.

When you create a new Car instance (my_car = Car()), it automatically creates an Engine instance as well. The Car class also has a start method that prints “Car starting” and then calls the start method of the Engine instance (self.engine.start()), which in turn prints “Engine starting”.

So, when you run my_car.start(), it first prints “Car starting” and then “Engine starting”, demonstrating how composition allows the Car class to use the functionality of the Engine class without inheritance.

Example 2: Employee and Address

Let us take another example and understand composition

class Address:
    def __init__(self, city, street):
        self.city = city
        self.street = street

class Employee:
    def __init__(self, name, address):
        self.name = name
        self.address = address

    def print_info(self):
        print(f"Name: {self.name}")
        print(f"Address: {self.address.city}, {self.address.street}")

# Usage
address = Address("New York", "123 Main St")
employee = Employee("John Doe", address)
employee.print_info()

Here, Employee contains an Address object, demonstrating how composition can represent real-world relationships between objects.

Explanation of the above code:

In the above code, we have two classes: Address and Employee. The Address class represents a physical address with attributes for the city and street. The Employee class represents an employee with attributes for the name and address.

When you create an instance of the Address class (address = Address(“New York”, “123 Main St”)), you provide values for the city and street. Then, when you create an instance of the Employee class (employee = Employee(“John Doe”, address)), you provide the employee’s name and the Address instance you created earlier.

The Employee class has a method called print_info that prints the employee’s name and address. When you call employee.print_info(), it prints:

Name: John Doe
Address: New York, 123 Main St

This demonstrates how objects can be composed of other objects in object-oriented programming.

Inheritance

Inheritance in OOP is like passing down traits and behaviors from parents to children. It lets new classes (children) inherit features from existing classes (parents), making it easier to reuse code and create new classes based on existing ones. However, it can make class structures inflexible and overly connected.

Example 3: Animal Hierarchy

class Animal:
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        print("Woof!")

class Cat(Animal):
    def speak(self):
        print("Meow!")

# Usage
dog = Dog()
cat = Cat()
dog.speak()
cat.speak()

In this example, Dog and Cat inherit the speak method from Animal, showcasing inheritance in action.

Explanation of the above code:

In the above code, we have a base class called Animal with a method speak that is not implemented (pass). Then, we have two subclasses, Dog and Cat, that inherit from Animal and implement their own version of the speak method. When we create instances of Dog and Cat and call their speak methods, they each make their respective sounds, “Woof!” for Dog and “Meow!” for Cat.

Furthermore, let’s consider a real-world example to understand the benefits of using composition over inheritance.

Imagine we have a Person class that represents a person with basic attributes like name and age. We also have a Job class that represents a person’s job with attributes like title and salary. Instead of using inheritance, where a Person class could inherit from a Job class, we can use composition to model the relationship between a person and their job more effectively.

Using composition, we can create a Person class that has a Job object as one of its attributes. This way, a person can have a job, but the person and the job are separate entities, and changes to one do not affect the other.

class Job:
    def __init__(self, title, salary):
        self.title = title
        self.salary = salary

class Person:
    def __init__(self, name, age, job=None):
        self.name = name
        self.age = age
        self.job = job

    def set_job(self, title, salary):
        self.job = Job(title, salary)

    def get_job_info(self):
        if self.job:
            return f"{self.name} works as a {self.job.title} earning ${self.job.salary}"
        else:
            return f"{self.name} does not have a job"

# Usage
john = Person("John", 30)
john.set_job("Engineer", 80000)

print(john.get_job_info())

In this example, the Person class has a job attribute that can be set using the set_job method. This allows us to model the relationship between a person and their job without using inheritance. If we had used inheritance, it would have been more rigid, as a person would always be tied to a specific type of job, which may not always be the case in the real world.

What is the difference between Composition & Inheritance?

Key Differences

  1. Relationship: Composition represents a “has-a” relationship, where an object contains another object. Inheritance represents an “is-a” relationship, where a subclass is a type of superclass.
  2. Flexibility: Composition provides more flexibility as objects can be replaced at runtime. Inheritance is more rigid as it creates a fixed class hierarchy.
  3. Code Reuse: Both allow for code reuse, but composition tends to be more flexible and avoids some of the issues associated with deep class hierarchies.

Composition and inheritance are powerful concepts in OOP, each with its strengths and use cases. Composition is often preferred over inheritance due to its flexibility and avoidance of the issues related to deep class hierarchies. Understanding when to use each approach is key to writing maintainable and flexible code.

Author

Sona Avatar

Written by

Leave a Reply

Trending

CodeMagnet

Your Magnetic Resource, For Coding Brilliance

Programming Languages

Web Development

Data Science and Visualization

Career Section

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4205364944170772"
     crossorigin="anonymous"></script>