Composition in Python

In the world of object-oriented programming, two fundamental concepts for building complex systems are inheritance and composition. While inheritance models an “is-a” relationship, composition models a “has-a” relationship. This blog post explores the concept of composition in Python, demonstrating how to create complex objects from simpler ones, thereby making your code more modular and maintainable.

What is Composition?

Composition is a design principle that allows us to build complex objects by combining simpler, independent objects. This is different from inheritance, where a subclass inherits properties and behaviors from a parent class. In composition, an object contains other objects, allowing for greater flexibility and reusability of code.

Inheritance vs. Composition

  • Inheritance: Represents an “is-a” relationship. For example, a Book is a Publication.
  • Composition: Represents a “has-a” relationship. For example, a Book has an Author.

Benefits of Composition

  • Modularity: By breaking down a monolithic class into smaller, manageable pieces, you can create more modular code.
  • Reusability: Components can be reused across different parts of the application.
  • Maintainability: Easier to maintain and extend as each component handles its own responsibilities.

Implementing Composition in Python

Let’s illustrate composition with an example. Suppose we have a Book class that contains details about the book and its author, and we want to separate the author’s information into its own class.

Step-by-Step Example

  1. Define the Author Class

    First, we create an Author class to encapsulate the author’s details.

				
					class Author:
    def __init__(self, fname, lname):
        self.fname = fname
        self.lname = lname

    def __str__(self):
        return f'{self.fname} {self.lname}'

				
			

Define the Chapter Class

Next, we create a Chapter class to represent individual chapters in a book.

				
					class Chapter:
    def __init__(self, name, pagecount):
        self.name = name
        self.pagecount = pagecount

				
			

Define the Book Class Using Composition

We now define the Book class, which will use composition to include Author and Chapter objects.

				
					class Book:
    def __init__(self, title, price, author=None):
        self.title = title
        self.price = price
        self.author = author
        self.chapters = []

    def add_chapter(self, chapter):
        self.chapters.append(chapter)

    def get_book_page_count(self):
        return sum(chapter.pagecount for chapter in self.chapters)

				
			

Create and Use the Objects

Finally, we create instances of Author, Chapter, and Book, demonstrating how they work together using composition.

				
					if __name__ == "__main__":
    # Create an author
    author = Author("Leo", "Tolstoy")

    # Create a book with the author
    book = Book("War and Peace", 39.95, author)

    # Add chapters to the book
    book.add_chapter(Chapter("Chapter 1", 125))
    book.add_chapter(Chapter("Chapter 2", 200))
    book.add_chapter(Chapter("Chapter 3", 150))

    # Print book details
    print(f"Book: {book.title}")
    print(f"Author: {book.author}")
    print(f"Total Pages: {book.get_book_page_count()}")

				
			

Running the Code

When you run the code, you will see the following output:

				
					Book: War and Peace
Author: Leo Tolstoy
Total Pages: 475

				
			

Conclusion

Composition allows you to build complex objects by combining simpler ones, leading to more modular, reusable, and maintainable code. While inheritance is a powerful tool for creating class hierarchies, composition provides flexibility by modeling a “has-a” relationship. By leveraging both inheritance and composition appropriately, you can create robust and extensible software systems.

Understanding and applying composition can significantly enhance your Python programming skills, enabling you to write cleaner, more efficient code.