All subjects · Computer Science · WCP04 Advanced Practical Programming and Problem-solving

Inheritance, polymorphism, aggregation, composition and UML

Edexcel IAL Computer Science · WCP04 Advanced Practical Programming and Problem-solving

Classes can be built from other classes. Inheritance shares code between related classes, polymorphism lets one call behave correctly for many types, and aggregation and composition build objects out of other objects. UML class diagrams describe all of this, and you must turn them into code.

Practise this in the app: instant marking and a fix for every mistake. Free, no email needed to start.

Practise this lesson

Inheritance

A subclass (child) inherits the attributes and methods of its superclass (parent) and can add its own: class Employee(Person):.

Polymorphism, aggregation and composition

class Person:
    def __init__(self, name):
        self.name = name

    def describe(self):
        return f"{self.name}"

class Employee(Person):
    def __init__(self, name, salary):
        super().__init__(name)           # reuse the parent's constructor
        self._salary = salary

    def describe(self):                  # overrides Person.describe
        return f"{self.name}, salary {self._salary}"

class Department:
    def __init__(self, title):
        self.title = title
        self.staff = []                  # aggregation: holds existing Employee objects

    def add(self, employee):
        self.staff.append(employee)

    def payroll(self):
        return sum(e._salary for e in self.staff)

people = [Person("Ira"), Employee("Jo", 30000), Employee("Ky", 32000)]
for p in people:
    print(p.describe())                  # polymorphism: the right method for each object
sales = Department("Sales")
sales.add(people[1])
sales.add(people[2])
print(sales.title, sales.payroll(), isinstance(people[1], Person))

Output:

Ira
Jo, salary 30000
Ky, salary 32000
Sales 62000 True

More polymorphism and composition

class Engine:
    def __init__(self, power):
        self.power = power

class Car:
    def __init__(self, model, power):
        self.model = model
        self.engine = Engine(power)      # composition: the car creates and owns its engine

class Shape:
    def area(self):
        return 0

class Square(Shape):
    def __init__(self, side):
        self.side = side
    def area(self):
        return self.side * self.side

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return round(3.14159 * self.r * self.r, 2)

print(Car("Hatch", 90).engine.power, [s.area() for s in [Square(3), Circle(1), Shape()]])

Output:

90 [9, 3.14, 0]

UML class diagrams

A class is drawn as a box with three sections: the class name, the attributes, and the methods.

UMLPython
Class Employee, inheriting from Person`class Employee(Person):`
- salary: int`self.__salary` set in `__init__`
# grade: str`self._grade`
+ describe(): str`def describe(self):` returning a str
Department, hollow diamond, 0..* Employee`self.staff = []` filled by an `add(employee)` method
Car, filled diamond, 1 Engine`self.engine = Engine(...)` inside `Car.__init__`

Worked example

A UML diagram shows class Student inheriting from Person, with '- grade: int' and '+ describe(): str' that adds the grade to Person's description. Write Student.

  1. ```python class Person: def __init__(self, name): self.name = name def describe(self): return f"{self.name}" class Employee(Person): def __init__(self, name, salary): super().__init__(name) # reuse the parent's constructor self._salary = salary def describe(self): # overrides Person.describe return f"{self.name}, salary {self._salary}" class Student(Person): def __init__(self, name, grade): super().__init__(name) self.__grade = grade def describe(self): return super().describe() + f", grade {self.__grade}" print(Student("Lin", 12).describe()) ```
  2. Output: ``` Lin, grade 12 ```

Answer: super() is used twice: to set the inherited name, and to reuse the parent's describe() before adding to it. The minus sign in UML becomes a double-underscore private attribute.

Key idea: Subclass(Parent) inherits; super().__init__ reuses the parent constructor; overriding replaces a method; polymorphism: the same call runs each object's own method; aggregation holds independent objects (hollow diamond); composition creates owned parts (filled diamond); UML + public, - private, # protected.

Check yourself

  1. The Shape, Square and Circle classes from the lesson are included in the code below. What does this print? ```python class Engine: def __init__(self, power): self.power = power class Car: def __init__(self, model, power): self.model = model self.engine = Engine(power) # composition: the car creates and owns its engine class Shape: def area(self): return 0 class Square(Shape): def __init__(self, side): self.side = side def area(self): return self.side * self.side class Circle(Shape): def __init__(self, r): self.r = r def area(self): return round(3.14159 * self.r * self.r, 2) shapes = [Square(2), Circle(2), Square(1)] print(sum(s.area() for s in shapes)) ```
    Show answer

    17.57. Each object uses its own area method: 4 + 12.57 + 1 = `17.57`

  2. The Person, Employee and Department classes from the lesson are included in the code below. What does this print? ```python class Person: def __init__(self, name): self.name = name def describe(self): return f"{self.name}" class Employee(Person): def __init__(self, name, salary): super().__init__(name) # reuse the parent's constructor self._salary = salary def describe(self): # overrides Person.describe return f"{self.name}, salary {self._salary}" class Department: def __init__(self, title): self.title = title self.staff = [] # aggregation: holds existing Employee objects def add(self, employee): self.staff.append(employee) def payroll(self): return sum(e._salary for e in self.staff) e = Employee("Ama", 25000) print(e.describe(), Person.describe(e)) ```
    Show answer

    Ama, salary 25000 Ama. e.describe() runs the overriding Employee method; calling Person.describe(e) directly runs the parent's version. Output: `Ama, salary 25000 Ama`

  3. The Person, Employee and Department classes from the lesson are included in the code below. What does this print? ```python class Person: def __init__(self, name): self.name = name def describe(self): return f"{self.name}" class Employee(Person): def __init__(self, name, salary): super().__init__(name) # reuse the parent's constructor self._salary = salary def describe(self): # overrides Person.describe return f"{self.name}, salary {self._salary}" class Department: def __init__(self, title): self.title = title self.staff = [] # aggregation: holds existing Employee objects def add(self, employee): self.staff.append(employee) def payroll(self): return sum(e._salary for e in self.staff) d = Department("IT") e = Employee("Bo", 40000) d.add(e) d.add(Employee("Cy", 35000)) del d print(e.name, e._salary) ```
    Show answer

    Bo 40000. Aggregation: the department only held references. Deleting d leaves the employee object e untouched. Output: `Bo 40000`

  4. The Person, Employee and Department classes from the lesson are included in the code below. What does this print? ```python class Person: def __init__(self, name): self.name = name def describe(self): return f"{self.name}" class Employee(Person): def __init__(self, name, salary): super().__init__(name) # reuse the parent's constructor self._salary = salary def describe(self): # overrides Person.describe return f"{self.name}, salary {self._salary}" class Department: def __init__(self, title): self.title = title self.staff = [] # aggregation: holds existing Employee objects def add(self, employee): self.staff.append(employee) def payroll(self): return sum(e._salary for e in self.staff) p = Person("Di") e = Employee("Ed", 1) print(isinstance(e, Person), isinstance(p, Employee)) ```
    Show answer

    True False. Every Employee is a Person, but a Person is not necessarily an Employee. Output: `True False`

  5. What does `super().__init__(name)` do in Employee's constructor?
    • Creates a new Person object separate from the Employee
    • Runs Person's constructor so the inherited name attribute is set
    • Makes name a private attribute
    • Calls Employee's constructor again
    Show answer

    Runs Person's constructor so the inherited name attribute is set. It reuses the parent's initialisation code on the same object.

  6. A School object creates its own Timetable object in its constructor, and the timetable has no use without that school. Which relationship is this?
    • Aggregation
    • Inheritance
    • Polymorphism
    • Composition
    Show answer

    Composition. The part is created and owned by the whole and does not exist independently: composition (filled diamond in UML).

  7. In a UML class diagram, what does '- score: int' in the attribute section mean?
    • A public attribute called score
    • A private attribute called score of type int
    • A method that subtracts from score
    • A protected attribute called score
    Show answer

    A private attribute called score of type int. The minus sign means private; in Python, write self.__score.

  8. How is inheritance shown between two classes on a UML class diagram?
    • A line with a hollow triangle pointing at the parent class
    • A line with a filled diamond at the parent class
    • A dotted line with no arrowhead
    • A line with a hollow diamond at the child class
    Show answer

    A line with a hollow triangle pointing at the parent class. The hollow triangle arrowhead points from the subclass to the superclass.

Practise with instant feedback

Sources

Last updated 8 November 2026. Spotted a mistake? Tell us.