← AS/A Level

CS · Chapter 27

Cambridge AS & A Level · Part 4 · Chapter 27

Object-Oriented Programming (OOP)

Group the data and the code that works on it into one package, then build new classes from old ones. Every figure, worked example, task and exam question from the chapter is here, in Python, VB.NET and Java, with labs where you create and use real objects.

Guru Jazzy
Show code in: The syllabus lets you write OOP in any one of these. Every listing below switches together.
Chapter 27 opener: Object-Oriented Programming (OOP)

🎯 Learning objectives

By the end of this chapter you should be able to:

  • show understanding of the terminology of OOP: objects, properties, methods, classes, inheritance, polymorphism, containment/aggregation, encapsulation, getters, setters and instances
  • show understanding of how to solve a problem by designing appropriate classes
  • show understanding of, and write, code that demonstrates the use of OOP

🧭 27.01 From records to objects

Chapters 14 and 26 used the procedural side of our languages. Procedural programming groups related statements into subroutines, and groups related data items into records. To use a record variable we first define a record type, then declare variables of that type.

Example of using a record

A car manufacturer and seller wants to store details about cars. These can go in a record structure (Chapter 16, Section 16.01 and Chapter 26, Section 26.02). We can then write a procedure that updates a field:

Pseudocode
TYPE CarRecord
    DECLARE VehicleID          : STRING
    DECLARE Registration       : STRING
    DECLARE DateOfRegistration : DATE
    DECLARE EngineSize         : INTEGER
    DECLARE PurchasePrice      : CURRENCY
ENDTYPE

PROCEDURE UpdateRegistration(BYREF ThisCar : CarRecord, BYVALUE NewRegistration)
    ThisCar.Registration ← NewRegistration
ENDPROCEDURE

// ...but nothing stops this, anywhere ThisCar is in scope:
ThisCar.EngineSize ← 2500

We can call UpdateRegistration from anywhere in the program, which looks like a well-regulated way of changing the record. But the record's fields can also be changed directly, from anywhere ThisCar is in scope: nothing protects them.

Classes in OOP

OOP goes one step further. It groups together the data structure and the subroutines that operate on it. That group is an object. The object's data are its attributes; the subroutines acting on the attributes are its methods.

The idea is that attributes can only be reached through methods: the direct path to the data is closed. Attributes are private; the methods are made available to programmers, so they are public. Combining the data with the subroutines that act on it is encapsulation.

To use an object we first define an object type, called a class. A class is a template: once it is defined, we can create one or more objects of that class. So an object is an instance of a class.

Object: an instance of a class
Attributes: the data items of a class (also sometimes called fields)
Methods: the subroutines of a class
Encapsulation: combining data and subroutines into a class and restricting external access to the data
Class: a type that combines a data structure with the methods that operate on the data structure

The first stage of writing an object-oriented program is to design the classes (object-oriented design). From that design, the program is written in an OOP language. The syllabus allows Python 3, VB.NET and Java.

Advantages of OOP over procedural languages

  • It produces robust, more reliable code.
  • Attributes can only be changed through the methods in the class definition, so they are protected from accidental changes.
  • Classes in module libraries are thoroughly tested. Building from tried-and-tested blocks means fewer new bugs than writing everything from scratch.

🔒 Record vs object

Same data, two ways of holding it. Try to change the engine size directly in each.

🔬 Lab: who can touch the data?

Record ThisCar : CarRecord

Object ThisCar = Car("ABC1234", 2500)

Press a button.

🎮 Name that term

Is it a class, an object, an attribute, a method, or encapsulation?

🏗️ 27.02 Designing classes and objects

When designing a class, think about the attributes to store and the methods needed to read the data and assign values to it.

A data type is a blueprint for declaring a variable of that type. A class definition is a blueprint for declaring an object of that class. Creating a new object is called instantiation.

  • Data held about an object must be accessible, or there is no point storing it. So each attribute needs a method that reads it: a getter.
  • When an object is first set up, a constructor instantiates it and gives the attributes their first values.
  • Attributes that may change after instantiation need methods to update them: setters. Attributes that are set only at instantiation get no setter. That makes the object more robust, because nobody can change what was not designed to change.
  • Python and VB.NET also allow properties: a property combines the attribute with its setter and/or getter.
Getter: a method to access its associated attribute
Constructor: a special type of method that is called to create a new object and initialise its attributes
Setter: a method to set the value of its associated attribute
Property: a class member that includes the attribute and also getter and setter method calls to access/set the attribute value
Worked Example 27.01

Creating a class

Back to the car data from Section 27.01. When a car is manufactured it gets a unique vehicle ID that never changes. The engine size is fixed when it is made. The registration ID is given when the car is sold.

When a car is manufactured, the program creates a new car object with the constructor. Anything already known at that moment can be set by the constructor: here, VehicleID and EngineSize. The other attributes get values at purchase and registration, so they need setters.

IdentifierData typeDescription
CarClassClass identifier
VehicleIDSTRINGUnique ID assigned at time of manufacture
RegistrationSTRINGUnique ID assigned after time of purchase
DateOfRegistrationDATEDate of registration
EngineSizeINTEGEREngine size assigned at time of manufacture
PurchasePriceCURRENCYPurchase price assigned at time of purchase
Constructor()Method to create a Car object and set properties assigned at manufacture
SetPurchasePrice()Method to assign purchase price at time of purchase
SetRegistration()Method to assign registration ID
SetDateOfRegistration()Method to assign date of registration
GetVehicleID()Method to access vehicle ID
GetRegistration()Method to access registration ID
GetDateOfRegistration()Method to access date of registration
GetEngineSize()Method to access engine size
GetPurchasePrice()Method to access purchase price

Table 27.01 Identifier table for the Car class

The same information drawn as a class diagram: class name at the top, attributes (private) in the middle, methods (public) at the bottom.

Figure 27.01Car class diagram: private attributes in the top half, public methods in the bottom half

🎮 Getter only, or setter too?

Decide whether each attribute should be set once by the constructor (getter, no setter) or needs a setter as well.

⌨️ 27.03 Writing object-oriented code

Declaring a class

Attributes should always be declared Private, so only the class's own methods can reach them. The methods must be declared Public so the main program can call them. Other modifiers exist (such as Protected), but they are beyond this book.

The syntax is quite different in each language. You are expected to write programs in one of them.

Declaring the Car class

class Car:
    def __init__(self, n, e):                # constructor
        self.__VehicleID = n
        self.__Registration = ""
        self.__DateOfRegistration = None
        self.__EngineSize = e
        self.__PurchasePrice = 0.00

    def SetPurchasePrice(self, p):
        self.__PurchasePrice = p
    def SetRegistration(self, r):
        self.__Registration = r
    def SetDateOfRegistration(self, d):
        self.__DateOfRegistration = d

    def GetVehicleID(self):
        return(self.__VehicleID)

    def GetRegistration(self):
        return(self.__Registration)

    def GetDateOfRegistration(self):
        return(self.__DateOfRegistration)

    def GetEngineSize(self):
        return(self.__EngineSize)

    def GetPurchasePrice(self):
        return(self.__PurchasePrice)
Read the Python carefully. Two underscores before and after init define the constructor. self is the first parameter of every method. Two underscores before an attribute name (self.__EngineSize) make it private. VB.NET: each attribute starts with Private, every public method header starts with Public, and the constructor is always called New. Java: the constructor has the same name as the class.

Properties (Python and VB.NET)

The Car class using properties

class Car:
    def __init__(self, n, e):                # constructor
        self.__VehicleID = n
        self.__Registration = ""
        self.__DateOfRegistration = None
        self.__EngineSize = e
        self.__PurchasePrice = 0.00

    @property
    def VehicleID(self):
        return(self.__VehicleID)

    @property
    def EngineSize(self):
        return(self.__EngineSize)

    @property
    def Registration(self):
        return(self.__Registration)

    @Registration.setter
    def Registration(self, r):
        self.__Registration = r

    @property
    def DateOfRegistration(self):
        return(self.__DateOfRegistration)

    @DateOfRegistration.setter
    def DateOfRegistration(self, d):
        self.__DateOfRegistration = d

    @property
    def PurchasePrice(self):
        return(self.__PurchasePrice)

    @PurchasePrice.setter
    def PurchasePrice(self, p):
        self.__PurchasePrice = p

# Not every property has a setter: only the attributes whose values are meant to change.

Java has no properties, so it uses the getter and setter methods above.

Instantiating a class

To use an object, it must first be instantiated: memory is reserved to store its attributes. This creates an object ThisCar of class Car:

ThisCar = Car("ABC1234", 2500)

Using a method

To call a method, write the object identifier, a dot, the method identifier and the parameter list. This sets the purchase price, then gets and prints the vehicle ID:

ThisCar.SetPurchasePrice(12000)
ThisCar.PurchasePrice = 12000      # using properties

print(ThisCar.GetVehicleID())
print(ThisCar.VehicleID)           # using properties

🚗 Lab: build and use Car objects

🔬 The Car class, live

Each press of the constructor button makes a new object of the same class. Pick an object, then call its methods. The private attributes are shown so you can see what each method does, but code outside the class cannot touch them.

✏️ Task 27.01

1 Copy the Car class definition into your program editor and write a simple program to test that each method works.
2 A business wants to store data about companies it supplies. The data includes: company name, email address, date of last contact.
  1. Design a class Company and draw a class diagram.
  2. Write program code to declare the class. Company name and email address are to be set by the constructor and will never be changed.
  3. Instantiate one object of this class and test your class code works.

(a) Class diagram:

+-----------------------------------+
|              Company              |
+-----------------------------------+
| CompanyName       : STRING        |
| EmailAddress      : STRING        |
| DateOfLastContact : DATE          |
+-----------------------------------+
| Constructor()                     |
| SetDateOfLastContact()            |
| GetCompanyName()                  |
| GetEmailAddress()                 |
| GetDateOfLastContact()            |
+-----------------------------------+

Only DateOfLastContact has a setter: the other two never change after the constructor sets them.

(b) and (c)

Python
import datetime

class Company:
    def __init__(self, n, e):                 # name and email never change
        self.__CompanyName = n
        self.__EmailAddress = e
        self.__DateOfLastContact = None

    def SetDateOfLastContact(self, d):
        self.__DateOfLastContact = d

    def GetCompanyName(self):
        return self.__CompanyName

    def GetEmailAddress(self):
        return self.__EmailAddress

    def GetDateOfLastContact(self):
        return self.__DateOfLastContact

# (c) instantiate one object and test every method
Supplier = Company("Acme Paper", "sales@acme.example")
Supplier.SetDateOfLastContact(datetime.date(2026, 10, 29))
print(Supplier.GetCompanyName())          # Acme Paper
print(Supplier.GetEmailAddress())         # sales@acme.example
print(Supplier.GetDateOfLastContact())    # 2026-10-29

🧬 27.04 Inheritance

A big advantage of OOP: design a class (a base class or superclass), then derive further classes (subclasses) from it. The base-class code is written once; the subclasses use its attributes and methods, and add their own. This is inheritance, drawn as an inheritance diagram.

Figure 27.02Inheritance diagram: (a) standard and (b) alternative way of drawing the arrows to the base class
Inheritance: all attributes and methods of the base class are copied to the subclass
Worked Example 27.02

Implementing a library system

  • A college library has items for loan.
  • The items are currently books and CDs.
  • Items can be borrowed for three weeks.
  • If a book is on loan, it can be requested by another borrower.
Library item
BookCD
Title of book*Title of CD*
Author of book*Artist of CD*
Unique library reference number*Unique library reference number*
Whether it is on loan*Whether it is on loan*
The date the book is due for return*The date the CD is due for return*
Whether the book is requested by another borrowerThe type of music on the CD (genre)

Table 27.02 Library system information

Look for what is shared. One variable Title can hold a book title or a CD title, depending on the item. The items marked * are similar in both. Some data differs: books can be requested; for CDs, the genre is stored.

So we define a class LibraryItem and derive a Book class and a CD class from it.

Figure 27.03Inheritance diagram for LibraryItem, Book and CD
Figure 27.04Class diagram for LibraryItem, Book and CD: shared members in the base class, specific ones in each subclass

A base class that is never used to create objects directly is an abstract class. LibraryItem is abstract: the library only ever holds books and CDs.

Abstract class: a base class that is never used to create objects directly

⌨️ Declaring a base class and subclasses

LibraryItem, Book and CD

import datetime
class LibraryItem:                        # the base class definition
    def __init__(self, t, a, i):          # initialiser method
        self.__Title = t
        self.__Author_Artist = a
        self.__ItemID = i
        self.__OnLoan = False
        self.__DueDate = datetime.date.today()

    def GetTitle(self):
        return(self.__Title)
    # other Get methods go here

    def Borrowing(self):
        self.__OnLoan = True
        self.__DueDate = datetime.date.today() + datetime.timedelta(weeks=3)

    def Returning(self):
        self.__OnLoan = False

    def PrintDetails(self):
        print(self.__Title, '; ', self.__Author_Artist, '; ', end='')
        print(self.__ItemID, '; ', self.__OnLoan, '; ', self.__DueDate)

class Book(LibraryItem):                  # a subclass definition
    def __init__(self, t, a, i):          # initialiser method
        LibraryItem.__init__(self, t, a, i)   # calls the base class constructor
        self.__IsRequested = False
        self.__RequestedBy = 0

    def GetIsRequested(self):
        return(self.__IsRequested)

    def SetIsRequested(self):
        self.__IsRequested = True

class CD(LibraryItem):
    def __init__(self, t, a, i):          # initialiser method
        LibraryItem.__init__(self, t, a, i)
        self.__Genre = ""

    def GetGenre(self):
        return(self.__Genre)

    def SetGenre(self, g):
        self.__Genre = g
The key lines. Python: class Book(LibraryItem): names the base class in brackets, and LibraryItem.__init__(self, t, a, i) calls the base class constructor. VB.NET: Inherits LibraryItem must be the first statement of the subclass. Java: class Book extends LibraryItem, and super(t, a, i); calls the base class constructor.

The same classes using properties

class LibraryItem:
    def __init__(self, t, a, i):      # constructor / initialiser method
        self.__Title = t
        self.__Author_Artist = a
        self.__ItemID = i
        self.__OnLoan = False
        self.__DueDate = datetime.date.today()

    @property
    def Title(self):
        return(self.__Title)
    # other getters go here

    def Borrowing(self):
        self.__OnLoan = True
        self.__DueDate = datetime.date.today() + datetime.timedelta(weeks=3)

    def Returning(self):
        self.__OnLoan = False

    def PrintDetails(self):
        print(self.__Title, '; ', self.__Author_Artist, '; ', end='')
        print(self.__ItemID, '; ', self.__OnLoan, '; ', self.__DueDate)

class Book(LibraryItem):
    def __init__(self, t, a, i):      # initialiser method
        LibraryItem.__init__(self, t, a, i)
        self.__IsRequested = False
        self.__RequestedBy = 0

    @property
    def IsRequested(self):
        return(self.__IsRequested)

    @IsRequested.setter
    def IsRequested(self, b):
        self.__IsRequested = b

    # print details method for Book
    def PrintDetails(self):
        print("Book Details")
        LibraryItem.PrintDetails(self)
        print(self.__IsRequested)

class CD(LibraryItem):
    def __init__(self, t, a, i):      # initialiser method
        LibraryItem.__init__(self, t, a, i)
        self.__Genre = ""

    @property
    def Genre(self):
        return(self.__Genre)

    @Genre.setter
    def Genre(self, g):
        self.__Genre = g

Instantiating a subclass

An object of a subclass is created exactly as for any class (Section 27.03):

ThisBook = Book(Title, Author, ItemID)
ThisCD = CD(Title, Artist, ItemID)

Using a method

An object created from a subclass is used exactly like an object of any other class: ThisBook.GetTitle() works, even though GetTitle is written only in LibraryItem.

🎮 Inherited, redefined or new?

After Task 27.04, where does each member of Book or CD come from?

✏️ Tasks 27.02 and 27.03

Task 27.02 Copy the class definitions for LibraryItem, Book and CD into your program editor. Write the additional get methods. Write a simple program to test that each method works.

Add GetAuthor_Artist(), GetItemID(), GetOnLoan() and GetDueDate() to LibraryItem, each one line: return self.__ItemID and so on. Then test both subclasses:

ThisBook = Book("Computing", "Sylvia", 1234)
ThisBook.Borrowing()
print(ThisBook.GetTitle(), ThisBook.GetOnLoan(), ThisBook.GetDueDate())
ThisBook.SetIsRequested()
print(ThisBook.GetIsRequested())        # True
ThisCD = CD("Let it be", "Beatles", 2345)
ThisCD.SetGenre("Rock")
print(ThisCD.GetGenre(), ThisCD.GetItemID())
Task 27.03 Write code to define a Borrower class as shown in the class diagram in Figure 27.05. The constructor should initialise ItemsOnLoan to 0. UpdateItemsOnLoan() should increment ItemsOnLoan by an integer passed as parameter. Write a simple program to test the methods.
Figure 27.05Borrower class diagram
Python
class Borrower:
    def __init__(self, n, e, i):
        self.__BorrowerName = n
        self.__EmailAddress = e
        self.__BorrowerID = i
        self.__ItemsOnLoan = 0                # the constructor sets this to 0

    def GetBorrowerName(self):
        return self.__BorrowerName
    def GetEmailAddress(self):
        return self.__EmailAddress
    def GetBorrowerID(self):
        return self.__BorrowerID
    def GetItemsOnLoan(self):
        return self.__ItemsOnLoan

    def UpdateItemsOnLoan(self, n):           # n = +1 when borrowing, -1 when returning
        self.__ItemsOnLoan = self.__ItemsOnLoan + n

    def PrintDetails(self):
        print(self.__BorrowerName, ';', self.__EmailAddress, ';',
              self.__BorrowerID, ';', self.__ItemsOnLoan)

# simple test program
Reader = Borrower("A. Smith", "xyz@abc.xx", 1001)
Reader.UpdateItemsOnLoan(2)
Reader.UpdateItemsOnLoan(-1)
print(Reader.GetItemsOnLoan())    # 1
Reader.PrintDetails()             # A. Smith ; xyz@abc.xx ; 1001 ; 1

🎭 27.05 Polymorphism

Look again at the library code. The constructor of the base class is redefined in each subclass: Book's constructor calls LibraryItem's and also initialises IsRequested; CD's calls LibraryItem's and also initialises Genre.

PrintDetails is only defined in the base class, so it can only print the base-class attributes. To include the subclass's extra attributes, we declare the method again in the subclass. It has the same identifier as the base-class method, but it behaves differently. This is polymorphism.

Polymorphism: the method behaves differently for different classes in the hierarchy

Each language redefines a method in its own way. The code below calls the base-class method with the same name first; you could also rewrite the method completely.

# define the print details method for Book
def PrintDetails(self):
    print("Book Details")
    LibraryItem.PrintDetails(self)     # calls the base class method with the same name
    print(self.__IsRequested)

🖥️ Lab: the library system (Task 27.07)

🔬 A working version of the menu in Figure 27.06

This runs the classes above (with Tasks 27.03–27.06 added) in the browser. Type a menu number and press Enter. Option 9 shows polymorphism: one loop calls PrintDetails() on every item, and each object runs its own class's version. A book and a CD are already in the library, and borrower 1 exists.

✏️ Tasks 27.04–27.07

Task 27.04 Use your program code from Task 27.02. Redefine the PrintDetails methods for the Book class and the CD class. Test your code.
Python
class Book(LibraryItem):
    # ...constructor and other methods as before...
    def PrintDetails(self):                   # redefines LibraryItem.PrintDetails
        print("Book Details")
        LibraryItem.PrintDetails(self)
        print("Requested:", self.__IsRequested)

class CD(LibraryItem):
    # ...constructor and other methods as before...
    def PrintDetails(self):
        print("CD Details")
        LibraryItem.PrintDetails(self)
        print("Genre:", self.__Genre)

# test: the same call behaves differently for each object (polymorphism)
for Item in [Book("Computing", "Sylvia", 1234), CD("Let it be", "Beatles", 2345)]:
    Item.PrintDetails()
Task 27.05 Use your program code from Task 27.03. Add another attribute, BorrowerID, to the LibraryItem class so that the item being loaned can have the borrower recorded. Change the LibraryItem.Borrowing and LibraryItem.Returning methods, so that LibraryItem.BorrowerID and Borrower.ItemsOnLoan are updated when a library item is borrowed or returned.

Pass the Borrower object into the method, so one call can update both objects. Each object still only changes its own attributes through its own methods.

Python
class LibraryItem:
    def __init__(self, t, a, i):
        # ...as before...
        self.__BorrowerID = 0                 # nobody has it yet

    def Borrowing(self, Who):                 # Who is a Borrower object
        self.__OnLoan = True
        self.__DueDate = datetime.date.today() + datetime.timedelta(weeks=3)
        self.__BorrowerID = Who.GetBorrowerID()
        Who.UpdateItemsOnLoan(1)

    def Returning(self, Who):
        self.__OnLoan = False
        self.__BorrowerID = 0
        Who.UpdateItemsOnLoan(-1)

    def GetBorrowerID(self):
        return self.__BorrowerID
Task 27.06 Use your code from Task 27.02 or Task 27.04. Add another attribute, RequestedBy, to the Book class so that the borrower making the request can be recorded. Change the method Book.SetIsRequested, so that Book.RequestedBy is updated when a book is requested.
Python
class Book(LibraryItem):
    def __init__(self, t, a, i):
        LibraryItem.__init__(self, t, a, i)
        self.__IsRequested = False
        self.__RequestedBy = 0

    def SetIsRequested(self, Who):            # Who is the Borrower making the request
        self.__IsRequested = True
        self.__RequestedBy = Who.GetBorrowerID()

    def GetRequestedBy(self):
        return self.__RequestedBy
Task 27.07 Use your code from Task 27.06 to write the complete program to implement a simplified library system. Write code to provide the user with a menu to choose an option. An example of a suitable menu is shown in Figure 27.06.

Figure 27.06 Library system menu

Pseudocode
1  - Add a new borrower
2  - Add a new book
3  - Add a new CD
4  - Borrow a book
5  - Return a book
6  - Borrow a CD
7  - Return a CD
8  - Request book
9  - Print all details
99 - Exit program
Enter your menu choice:

This uses the classes from Tasks 27.03–27.06, plus GetItemID() from Task 27.02. Try the same menu in the lab above.

Python
Borrowers, Items = [], []                    # lists of objects

def FindItem(i, Kind):
    for Item in Items:
        if Item.GetItemID() == i and isinstance(Item, Kind):
            return Item
    return None

def FindBorrower(b):
    for Who in Borrowers:
        if Who.GetBorrowerID() == b:
            return Who
    return None

Choice = 0
while Choice != 99:
    print("1 - Add a new borrower\n2 - Add a new book\n3 - Add a new CD")
    print("4 - Borrow a book\n5 - Return a book\n6 - Borrow a CD\n7 - Return a CD")
    print("8 - Request book\n9 - Print all details\n99 - Exit program")
    Choice = int(input("Enter your menu choice: "))
    if Choice == 1:
        Borrowers.append(Borrower(input("Name: "), input("Email: "), len(Borrowers) + 1))
    elif Choice == 2:
        Items.append(Book(input("Title: "), input("Author: "), len(Items) + 1))
    elif Choice == 3:
        NewCD = CD(input("Title: "), input("Artist: "), len(Items) + 1)
        NewCD.SetGenre(input("Genre: "))
        Items.append(NewCD)
    elif Choice in (4, 5, 6, 7):
        Kind = Book if Choice in (4, 5) else CD
        Item = FindItem(int(input("Item ID: ")), Kind)
        Who = FindBorrower(int(input("Borrower ID: ")))
        if Item is None or Who is None:
            print("Not found")
        elif Choice in (4, 6):
            Item.Borrowing(Who)
        else:
            Item.Returning(Who)
    elif Choice == 8:
        Item = FindItem(int(input("Book ID: ")), Book)
        Who = FindBorrower(int(input("Borrower ID: ")))
        if Item and Who:
            Item.SetIsRequested(Who)
    elif Choice == 9:
        for Item in Items:
            Item.PrintDetails()                # polymorphism: Book or CD version
        for Who in Borrowers:
            Who.PrintDetails()

🗑️ 27.06 Garbage collection

Objects take up memory when they are created. When they are no longer needed, that memory should be released so it can be reused. If objects never let go of memory, we eventually run out of free memory when we try to run a program. This is a memory leak.

LanguageHow memory is reclaimed
PythonMemory management involves a private heap containing all Python objects and data structures. The interpreter itself manages the heap; the programmer does not need to do any housekeeping.
VB.NETA garbage collector automatically reclaims memory from objects that are no longer referred to by the running program.
JavaThe Java runtime environment deletes objects when it determines that they are no longer being used.

Table 27.03 Garbage collection strategies

🔬 Lab: the heap

Six variables (a to f) can each refer to one object. Creating a new object reuses the next variable, so the object it pointed at before loses its last reference and becomes garbage (dashed red). A Course object refers to two Lesson objects, so they stay alive as long as the course does. Switch the garbage collector off to watch a leak.

🧩 27.07 Containment (aggregation)

Section 27.04 showed a subclass inheriting from a base class. That relationship is generalisation and specialisation: the base class is the most general, and its subclasses are more specialised.

Classes can be related in other ways. Containment means that one class contains other classes. A car is made up of parts, and each part is an object of a class: the wheels are objects of a different class from the engine. The engine is itself made of parts. Together, all these parts make up one big object.

Containment: a relationship in which one class has a component that is of another class type
Figure 27.07Containment (aggregation) class diagram: one Car contains many Wheels

The filled diamond sits at the containing class. The 1 and * say that one car has many wheels.

Quick test. Inheritance is an is-a relationship (a Book is a LibraryItem). Containment is a has-a relationship (a Car has Wheels).

🎮 Is-a or has-a?

Worked Example 27.03

Using containment

A college runs courses of up to 50 lessons. A course may end with an assessment. OOP is to be used to set up courses. The classes required are shown in Figure 27.08.

Figure 27.08Containment class diagram: a Course contains up to 50 Lessons and one Assessment

If all the attributes of Lesson and Assessment are set by values passed to the constructor, those two classes are straightforward. Here is the Course class:

Course class declaration

class Course:
    def __init__(self, t, m):          # sets up a new course
        self.__CourseTitle = t
        self.__MaxStudents = m
        self.__NumberOfLessons = 0
        self.__CourseLesson = []
        self.__CourseAssessment = None   # no Assessment object yet

    def AddLesson(self, t, d, r):
        self.__NumberOfLessons = self.__NumberOfLessons + 1
        self.__CourseLesson.append(Lesson(t, d, r))

    def AddAssessment(self, t, m):
        self.__CourseAssessment = Assessment(t, m)

    def OutputCourseDetails(self):
        print(self.__CourseTitle, "Maximum number: ", self.__MaxStudents)
        for i in range(self.__NumberOfLessons):
            self.__CourseLesson[i].OutputLessonDetails()

Simple test programs to check it works:

Test program

def Main():
    MyCourse = Course("Computing", 10)            # sets up a new course
    MyCourse.AddAssessment("Programming", 100)    # adds an assessment
    # add 3 lessons
    MyCourse.AddLesson("Problem Solving", 60, False)
    MyCourse.AddLesson("Programming", 120, True)
    MyCourse.AddLesson("Theory", 60, False)
    # check it all works
    MyCourse.OutputCourseDetails()

🏫 Lab: build a course

🔬 One object containing others

Create a course, add lessons and an assessment, then call OutputCourseDetails(). The course object holds the lesson and assessment objects inside it.

✏️ Task 27.08 and Reflection Point

Task 27.08 Write the code required for the Lesson and Assessment classes. Add the code for the Course class and test your program with the appropriate simple program from Worked Example 27.03.
Python
class Lesson:
    def __init__(self, t, d, r):
        self.__LessonTitle = t
        self.__DurationMinutes = d
        self.__RequiresLab = r

    def OutputLessonDetails(self):
        print(self.__LessonTitle, self.__DurationMinutes, "minutes",
              "(lab)" if self.__RequiresLab else "")

class Assessment:
    def __init__(self, t, m):
        self.__AssessmentTitle = t
        self.__MaxMarks = m

    def OutputAssessmentDetails(self):
        print("Assessment:", self.__AssessmentTitle, "out of", self.__MaxMarks)

# ...plus the Course class above. Main() then prints:
# Computing Maximum number:  10
# Problem Solving 60 minutes
# Programming 120 minutes (lab)
# Theory 60 minutes
Reflection Point: Explain why a class can be regarded as a user-defined type.

The programmer, not the language, defines it: its name, the data it holds (attributes and their types) and the operations allowed on that data (methods). Once defined, it is used exactly like a built-in type: you declare variables (objects) of that type, pass them as parameters and store them in arrays. It is the record type of Chapter 16 taken one step further, because the operations travel with the data.

📌 Summary

  • A class has attributes (declared as private) and methods (declared as public) that operate on the attributes. This is known as encapsulation.
  • Python and VB.NET support properties: attributes that also include getters and setters.
  • A class is a blueprint for creating objects.
  • An object is an instance of a class.
  • A constructor is a method that instantiates a new object.
  • A class and its attributes and methods can be represented by a class diagram.
  • Classes (subclasses) can inherit from another class (the base class or superclass). This relationship can be represented using an inheritance diagram.
  • A subclass has all the attributes and methods of its base class. It also has additional attributes and/or methods.
  • Polymorphism describes the different behaviour of a subclass method with the same name as the base class method.
  • Containment is a relationship between two classes where one class has a component that is of the other class type. This can be represented using a containment diagram.

🃏 Key-term flashcards

Tap a card to flip it.

✅ Quick quiz

📝 Exam-style questions

Write your answer first, then open the model answer. These are my mark-scheme-style points, not official mark schemes.

1 A program is to be written using an object-oriented programming language. A bank account class is designed. Two subclasses have been identified:
  • Personal Account: the account holder pays a monthly fee and may overdraw the account up to an agreed overdraft limit.
  • Savings Account: the account holder must maintain a positive balance and gets paid interest on the balance at an agreed interest rate.
The design for the BankAccount class consists of: attributes AccountHolderName and IBAN (International Bank Account Number); methods CreateNewAccount, SetAccountHolderName, GetAccountHolderName and GetIBAN.
            +-------------+
            | BankAccount |
            +-------------+
                   ▲
          +--------+---------+
          |                  |
+-----------------+  +----------------+
| PersonalAccount |  | SavingsAccount |
+-----------------+  +----------------+
  • 1 mark: BankAccount as the single base class at the top.
  • 1 mark: both subclasses, correctly named.
  • 1 mark: arrow(s) pointing from the subclasses to the base class.
CLASS BankAccount
    PRIVATE AccountHolderName : STRING
    PRIVATE IBAN : STRING

    PUBLIC PROCEDURE NEW(Name : STRING, Number : STRING)   // CreateNewAccount
        AccountHolderName ← Name
        IBAN ← Number
    ENDPROCEDURE

    PUBLIC PROCEDURE SetAccountHolderName(Name : STRING)
        AccountHolderName ← Name
    ENDPROCEDURE

    PUBLIC FUNCTION GetAccountHolderName() RETURNS STRING
        RETURN AccountHolderName
    ENDFUNCTION

    PUBLIC FUNCTION GetIBAN() RETURNS STRING
        RETURN IBAN
    ENDFUNCTION
ENDCLASS
  • Class header.
  • Both attributes declared private.
  • Constructor (CreateNewAccount) with two parameters that sets both attributes.
  • SetAccountHolderName with a parameter.
  • Both getters returning the attribute.
  • Attributes: MonthlyFee : CURRENCY, OverdraftLimit : CURRENCY (and Balance : CURRENCY).
  • Methods: SetMonthlyFee() / GetMonthlyFee(), SetOverdraftLimit() / GetOverdraftLimit(), a constructor, and a withdraw method that allows the balance to go down to minus the overdraft limit but no further.
  • Attributes: InterestRate : REAL (and Balance : CURRENCY).
  • Methods: SetInterestRate() / GetInterestRate(), AddInterest() (balance × rate), a constructor, and a withdraw method that refuses any withdrawal that would make the balance negative.

Encapsulation.

2 A bus company in a town has two types of season ticket for its regular customers: pay-as-you-go and contract. All season ticket holders have their name and email address recorded. A pay-as-you-go ticket holder pays a chosen amount into their account. Each time they make a journey on the bus, the price of the fare is deducted from the amount held in the account. They can top up the amount at any time. A contract ticket holder pays a fixed fee per month. They can then make unlimited journeys on the bus. The bus company wants a program to process the season ticket data. The program will be written using an object-oriented programming language.
Exam-style Question 2: class diagrams to complete
SeasonTicketHolder
  PRIVATE
    TicketHolderName : STRING
    EmailAddress     : STRING
  PUBLIC
    Constructor()
    GetTicketHolderName()
    GetEmailAddress()
    SetTicketHolderName()
    SetEmailAddress()

Pay-As-You-Go-TicketHolder              ContractTicketHolder
    Balance : CURRENCY                      MonthlyFee : CURRENCY

    Constructor(Name, email, Amount)        Constructor(Name : STRING, email : STRING, Fee : CURRENCY)
    TopUp(Amount : CURRENCY)                GetMonthlyFee()
    PayFare(Fare : CURRENCY)                SetMonthlyFee()
    GetBalance()

Marks for: EmailAddress in the base class; getters and setters in the base class; a balance attribute in pay-as-you-go; top-up and fare-deduction methods; a getter for the balance; a monthly-fee attribute in contract; a getter/setter for the fee. Do not repeat the name and email in the subclasses: they are inherited.

  • They can only be accessed through the class's own methods…
  • …so they cannot be changed accidentally or without authority by other code. This keeps the data valid (encapsulation).
  • So the main program (and other classes, including the subclasses) can call them…
  • …because the methods are the only way to read or update the private attributes.
NewCustomer = ContractTicketHolder("A. Smith", "xyz@abc.xx", 10.00)
  • Identifier NewCustomer assigned / declared.
  • Calls the ContractTicketHolder constructor (using New in VB.NET and Java).
  • The three values in the right order and of the right types.
3 A queue abstract data type (ADT) is to be implemented using object-oriented programming. Two classes have been identified: Queue and Node. The class diagrams are as follows:
Exam-style Question 3: QueueClass and NodeClass

Containment (aggregation): a QueueClass object contains NodeClass objects. It is not inheritance: a node is not a kind of queue.

Python
class NodeClass:                             # (b)
    def __init__(self):
        self.__Data = ""
        self.__Pointer = -1

    def SetData(self, d):
        self.__Data = d
    def SetPointer(self, x):
        self.__Pointer = x
    def GetData(self):
        return self.__Data
    def GetPointer(self):
        return self.__Pointer

class QueueClass:
    def __init__(self):                       # (c)
        self.__Queue = [NodeClass() for i in range(51)]   # ARRAY[0 : 50]
        self.__Head = -1
        self.__Tail = -1

    def JoinQueue(self, NewItem):             # (d)
        if self.__Tail == 50:
            print("Queue is full")
        else:
            Node = NodeClass()                # create a new object, Node
            Node.SetData(NewItem)             # put the value in its Data attribute
            self.__Tail = self.__Tail + 1
            self.__Queue[self.__Tail] = Node  # add Node to the end of Queue
            if self.__Head == -1:             # first item in an empty queue
                self.__Head = 0

The class diagram shows JoinQueue(NewItem : NodeClass), but part (d) says the value passed is put into the new node's Data, so NewItem is treated here as a STRING. Say which reading you used in the exam. This is a linear queue; a circular queue would wrap Tail back to 0.

🔬 Try the queue from Question 3

Eight slots here instead of 51. Each slot holds a NodeClass object.

🔧 Book corrections

The listings on this page fix these slips in the printed book:

  • Python Car: def GetVehicleID ID(self) → def GetVehicleID(self); @DataOfRegistration.setter → @DateOfRegistration.setter.
  • Python LibraryItem: timedelta(weeks-3) → weeks=3; melf.__Title → self; self.__OnLoan - False → = False; Self → self; SetLiRequested → SetIsRequested; GetGestre → GetGenre.
  • Python Borrowing added three weeks to the old due date; it now sets the due date to three weeks from today, like the VB.NET version.
  • Java borrowing: dueDate + 21 is not valid Java for a Date (the book comments it out). A real version would use Calendar.add(Calendar.DATE, 21) or LocalDate.plusWeeks(3).
  • Python Course: self.__CourseAssessment = Assessment stored the class itself, not an object, so it is now None. print(...OutputLessonDetails()) would also print None after each lesson, so the print is removed.
  • Java Course: lessons were stored from index 1 of a 50-element array (the 50th lesson would crash), and the loop i = 1; i < numberOfLessons skipped the last lesson. Both now count from 0.
  • Task 27.05 says LoanItem.BorrowerID; it means LibraryItem.BorrowerID. The Reflection Point says "use-defined type"; it means user-defined.

Guru Jazzy · Cambridge AS & A Level Computer Science · Chapter 27 Object-Oriented Programming · Figures from the Cambridge coursebook (Langfield & Duddell) · Model answers are my own