This is an archived version of the course. Please find the latest version of the course on the main webpage.

Chapter 2: Object interaction

Aggregation and composition

face Josiah Wang

In the previous exercise, Car and Driver are quite loosely dependent. A Driver instance will only have access to a Car in the drive() method (passed as an input argument).

But perhaps we might also want Driver to actually have some ownership towards the Car instance? Since he will be driving the car a lot?

In object-oriented programming, such “has-a” relationships are known as aggregation or composition. Some people also call this a “whole/part” relationship. So you can think of an object as being “part of” another object.

For example, your Hero might have a Hand and a Weapon, a Book might have an Author, a Car might have an Engine and a Wheel, a Webpage might have a Button and a SearchBar etc.

Naturally, the object ‘part’ will be an instance attribute/variable of the ‘whole’ object, so you would usually initialise them in the constructor of the ‘whole’ object.

There are two ways to implement composition/aggregation. You can pass in an existing instance as an input argument to the constructor (weapon in the code below), or you can construct a new object directly inside the constructor (Head() in the code below).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Hero:
    def __init__(self, name, health, strength, defence, luck, weapon):
        self.name = name
        self.health = health
        self.strength = strength
        self.defence = defence
        self.luck = luck
        self.weapon = weapon
        self.head = Head()

    def attack(self, enemy):
        spell_power = self.cast_spell()
        attack_power = self.strength * spell_power
        enemy.reduce_health(attack_power)      

    def cast_spell(self, spell_name="Expulso"):
        if spell_name == "Expulso":
            if self.weapon.is_charged():
                return 1.3
            else:
                return 1.2
        else:
            return 1.0

Unlike previously with enemy, I used the .is_charged() method of self.weapon in Line 14 without passing it as an input argument to cast_spell(), since Hero actually owns a weapon. In a sense, you can consider aggregation/composition to be stronger form of dependency compared to just passing the object as an input argument to a method. Think of your hero owning a weapon vs. picking up a weapon when needed.

A Head instance itself can also have more ‘parts’ (Eye, Nose, Mouth), and each can have their own ‘parts’ (an Eye might have an Eyebrow, Pupil, Iris etc. So you end up with a hierarchy of ‘parts’!

As you can see, composition/aggregation can be quite a powerful abstraction!