Classes - composition

Composition, like inheritance, is a technique for organizing functionalities across different classes. However, unlike inheritance, where a class directly inherits properties and methods from a parent class, composition involves constructing a class using objects of other classes. This means we pass an already initialized object as a parameter to another class. This method, sometimes referred to as 'mixins,' allows for greater flexibility and modularity, as it separates distinct functionalities into different classes.

For example, if we want to include songs in a music catalog, we might initially think of treating songs as a part of the Catalog class. However, suppose we decide that songs should be associated exclusively with albums. In this case, we can use composition. We first initialize an Album object. Then, when we create a Song instance, we pass this Album object as a parameter. This approach allows the Song to be associated with an Album without inheriting all the properties of the Album class directly. Additionally, let's also consider creating a function that prints all the songs in an album, a concept we'll explore more in a bit.

Can you guess which object needs to be instantiated before the other?

That's right, an album object must be created before a song! Instantiating an Album and a Song go as expected up until the line in the Song initialization that reads album.add(self). What this does is call the function add which is a member of the Album class. Passing self will actually pass a reference of the instance variables within the song object, which is named song in the add function; self in this function still refers to the instance variables within the album object.

One more note, since the songs list is contained within the album class, it's better for this class to manipulate it rather than others. For example we could have written album.songs.append(self) and it would have still worked, however as mentioned, its better to not.

Lets add a few more songs to the album and then see what our album looks like.

Wow, how did that work? when we call all_songs(), it starts with a string formatted with self. Since self is a reference to the object itself, it behaves the same as if we called str(album) on the object outside of the class. this is why the first line looks as it does. Next, we use a sorting algorithm to reorder the songs in song order. Details of sorting algorithms are covered in the intermediate software development course. In short, this function is built into Python with the goal of sorting an array. Since the items in our array are a little complicated we tell it how to sort by telling the key parameter how to do so; we use a lambda function to specify what the value should be used for sorting. Once its sorted, we simply loop through it and append to our string! Again, since the songs list is a list of song objects, calling string on it will call the __str__ we wrote within the Song class.

Wow, that was a lot. Classes in Python are one of its greatest strengths. As we saw, simply having multiple classes interact with each other allowed us to do a lot with relatively little code. Here's a quiz to help reinforce this subject.

Practice Question

Using the same code we've been working with, we now want to offer songs individually. By making Song inherit from Catalog, which choice is not required?

score: 0%