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.
class Album(Catalog):
def __init__(self, title, artist, release_year):
Catalog.__init__(self, title, release_year)
self.artist = artist
self.songs = []
def add(self, song):
self.songs.append(song)
def all_songs(self):
s = f"{self}\n"
songs = sorted(self.songs, key=lambda x: x.song_number)
for song in songs:
s += f" {song.song_number}. {song}\n"
return s
class Song:
def __init__(self, title, album, song_number):
self.title = title
self.album = album.title
self.song_number = song_number
album.add(self)
def __str__(self):
s = f"{self.title}"
return s
Can you guess which object needs to be instantiated before the other?
album = Album("Love Collection 2 Pink", "Kana Nishino", 2018)
song_01 = Song("Have a nice day", album, 1)
print(album)
> Love Collection 2 Pink (2018)
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%