Skip to content
OVEX TECH
Education & E-Learning

Build Better Code with Object Composition in Python

Build Better Code with Object Composition in Python

How to Build Better Code with Object Composition in Python

When computer programs get complicated, we often break them down into smaller pieces. We’ve already learned how to do this with functions and data structures. But how do you break down a complex object? Object composition is the answer. It’s a way to organize your code by making one object contain other, smaller objects. Each of these smaller objects handles a specific job. This tutorial will show you how to use object composition in Python to make your code cleaner, more organized, and easier to reuse.

What You’ll Learn

In this guide, you’ll learn the core idea of object composition. You’ll see how to access data and actions within nested objects. We’ll explore a real-world example to understand why composition is better than putting all the code into one big class. You’ll also discover how composition leads to more flexible and reusable code.

Prerequisites

  • Basic understanding of Python programming.
  • Familiarity with Python classes, objects, attributes, and methods.

Understanding Object Composition

Object composition is like building with LEGOs. Instead of having one giant, complex LEGO structure, you build smaller, independent LEGO pieces. Then, you connect these smaller pieces to create the final structure. In programming, object composition means an object contains other objects. This helps manage complexity by giving each object a clear responsibility.

Think of a music playlist. A playlist isn’t just a list of songs; it’s an object that *manages* a collection of song objects. Each song object, in turn, knows its own title and artist and how to play itself. The playlist object doesn’t need to know how to play a song; it just needs to know how to get the next song from its collection and tell that song object to play.

Step 1: Accessing Data in Nested Objects

Let’s imagine we have a Playlist class and a Song class. Our Playlist object might store a list of Song objects in an attribute called queue. Each Song object has attributes like title and artist, and a method called play.

Suppose you have a playlist object named my_playlist. How would you find out the title of the very next song in the queue? You’d first access the queue attribute of your my_playlist object using dot notation (my_playlist.queue). This gives you the list of songs.

Next, you need to pick the specific song you’re interested in from that list. If you want the first song, you’d use list indexing, like [0] (my_playlist.queue[0]). This action gives you a single Song object.

Finally, to get the title of that song, you use dot notation again on the Song object (my_playlist.queue[0].title). You can combine all these steps into one clear line of code to access the song’s title.

Step 2: Performing Actions with Nested Objects

What if you want to play that next song? The process is very similar to accessing the title. You follow the same steps to get to the specific Song object (my_playlist.queue[0]). But instead of accessing an attribute like title, you call the play method on that Song object.

So, the complete line of code might look like my_playlist.queue[0].play(). This demonstrates how the Playlist object delegates the task of playing a song to the Song object itself. This separation of responsibilities is a key benefit of composition.

Expert Tip: Delegation

The pattern where an object calls a method on another object it contains is called delegation. The outer object (like the playlist) delegates the specific task (like playing a song) to the inner object (like the song). This keeps the outer object’s code simple.

Step 3: Why Composition Beats a Single, Large Class

Let’s consider a different example: a mining game. Imagine a Character class that can mine blocks. The effectiveness of mining depends on the character’s strength and the tool’s power. After each mining action, the character’s stamina decreases, and the tool’s durability goes down.

If you put all this logic—character stats, tool stats, and mining actions—into a single Character class, you run into problems. First, the class becomes very large and confusing. It’s trying to represent both a character and a tool, making it hard to understand whose stats or actions are being referred to.

Second, games often let players swap tools. If tool logic is buried inside the Character class, it’s difficult to change the tool the character is using. You can’t easily switch from a pickaxe to a shovel.

Third, this approach hurts code reusability. Tools aren’t always tied to a character. You might find tools in a shop or just lying around. A single Character class doesn’t allow you to use the tool logic independently elsewhere.

Step 4: Applying Composition for Better Design

To fix these issues, we use object composition. We create a separate Tool class. The Character class will then have an attribute that holds a Tool object. This means the Character class no longer needs to store tool-specific attributes like durability or power.

Instead, we move all the tool-related attributes and methods (like knowing when it’s broken or how to use itself) into the new Tool class. The Character class can then simply ask its contained Tool object to perform actions like mining. This is called delegation again; the character delegates the tool’s work to the tool.

With this setup, tools become independent entities. You can create a Pickaxe object or a Shovel object separately. Your Character object can then be assigned any of these tool objects. If the character finds a better shovel, you can update the character’s tool with a single line of code, like character.tool = new_shovel.

Warning: Over-Composition

While composition is powerful, avoid creating too many tiny objects for simple tasks. Aim for a balance. If a piece of logic is very simple and tightly coupled to its parent object, it might not need its own separate class. Focus on composing objects when you have distinct responsibilities or when you anticipate needing to swap components.

Step 5: Gaining Flexibility and Reusability

This clean separation of concerns allows your programming team to work on the Character and Tool classes independently. If you need to change how tools lose durability, you can update the Tool class without worrying about breaking the Character class. This makes your code much easier to maintain and modify.

Furthermore, you can even break down the Tool class further using composition. For example, a Tool object could contain a Material object. This Material object might define the tool’s power and durability characteristics. This layered approach allows for incredible flexibility.

So, the next time you’re designing a program, think about the real-world concepts you need to represent. If your ideas start to get mixed up, or if a single class is becoming too long and complex, consider how object composition can help you break it down into smaller, manageable, and reusable parts.


Source: Object composition | Intro to CS – Python | Khan Academy (YouTube)

Leave a Reply

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

Written by

John Digweed

1,967 articles

Life-long learner.