Composition: Relationship between Classes

Composition is a fundamental design principle in object-oriented programming (OOP) that represents a “has-a” relationship between classes. This means that one class contains an object of another class, using it to build more complex and versatile functionality. Composition promotes code reuse, modularity, and maintainability, making it a powerful tool in software design.

What is Composition?

Composition involves creating complex types by combining objects of other types. Instead of inheriting behavior from a parent class (as in inheritance), a class achieves functionality by including instances of other classes. This relationship indicates that the containing class has an instance of the contained class.

Example: If you have a Car class that contains objects of Engine, Wheel, and Transmission classes, the Car class is said to be composed of these parts.

Benefits of Composition

  1. Reusability: Reusing existing classes to build new functionality.
  2. Flexibility: Changing the composed objects to alter the behavior of the containing class.
  3. Encapsulation: Keeping the internal details of composed objects hidden from the containing class.
  4. Maintainability: Easier to manage changes in the system by modifying individual components.

Composition vs. Inheritance

  • Inheritance: Represents an “is-a” relationship. A subclass inherits the behavior and properties of a parent class.
  • Composition: Represents a “has-a” relationship. A class is composed of one or more objects of other classes.

While inheritance is useful for creating a clear hierarchy and reusing code, it can lead to a rigid structure. Composition offers more flexibility by allowing objects to be composed and changed dynamically.

Implementing Composition

Let’s consider a practical example where a Car class is composed of Engine and Wheel classes.

Example:

class Engine:
    def __init__(self, horsepower):
        self.horsepower = horsepower

    def start(self):
        print("Engine started")

class Wheel:
    def __init__(self, size):
        self.size = size

    def rotate(self):
        print("Wheel is rotating")

class Car:
    def __init__(self, horsepower, wheel_size):
        self.engine = Engine(horsepower)
        self.wheels = [Wheel(wheel_size) for _ in range(4)]

    def start(self):
        self.engine.start()
        for wheel in self.wheels:
            wheel.rotate()

# Usage
my_car = Car(150, 16)
my_car.start()

In this example, the Car class contains an Engine object and a list of Wheel objects, illustrating the “has-a” relationship. The Car class can start its engine and rotate its wheels, leveraging the functionality provided by the composed objects.

More Advanced Example

Consider a scenario where a Library class is composed of Book and Librarian classes.

Example:

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

    def info(self):
        return f"{self.title} by {self.author}"

class Librarian:
    def __init__(self, name):
        self.name = name

    def assist(self):
        print(f"Librarian {self.name} is assisting you")

class Library:
    def __init__(self):
        self.books = []
        self.librarian = Librarian("Jane Doe")

    def add_book(self, book):
        self.books.append(book)
        print(f"Book '{book.title}' added to the library")

    def list_books(self):
        for book in self.books:
            print(book.info())

    def assist_patron(self):
        self.librarian.assist()

# Usage
library = Library()
book1 = Book("1984", "George Orwell")
book2 = Book("To Kill a Mockingbird", "Harper Lee")

library.add_book(book1)
library.add_book(book2)

library.list_books()
library.assist_patron()

In this example, the Library class is composed of a list of Book objects and a Librarian object. This demonstrates how composition can be used to create complex structures and relationships within a system.

Conclusion

Composition is a powerful design principle in OOP that enables building complex functionality through the aggregation of simpler objects. It promotes code reuse, flexibility, and maintainability by allowing objects to be composed, reused, and modified independently. Understanding and leveraging composition can lead to more modular and maintainable software designs.