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🎯 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:
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 ← 2500We 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.
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)
🎮 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.
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.
| Identifier | Data type | Description |
|---|---|---|
| Car | Class | Class identifier |
| VehicleID | STRING | Unique ID assigned at time of manufacture |
| Registration | STRING | Unique ID assigned after time of purchase |
| DateOfRegistration | DATE | Date of registration |
| EngineSize | INTEGER | Engine size assigned at time of manufacture |
| PurchasePrice | CURRENCY | Purchase 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.
🎮 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)Class Car
Private VehicleID As String
Private Registration As String = ""
Private DateOfRegistration As Date = #1/1/1900#
Private EngineSize As Integer
Private PurchasePrice As Decimal = 0.0
Public Sub New(ByVal n As String, ByVal e As String) ' the constructor is always New
VehicleID = n
EngineSize = e
End Sub
Public Sub SetPurchasePrice(ByVal p As Decimal)
PurchasePrice = p
End Sub
Public Sub SetRegistration(ByVal r As String)
Registration = r
End Sub
Public Sub SetDateOfRegistration(ByVal d As Date)
DateOfRegistration = d
End Sub
Public Function GetVehicleID() As String
Return (VehicleID)
End Function
Public Function GetRegistration() As String
Return (Registration)
End Function
Public Function GetDateOfRegistration() As Date
Return (DateOfRegistration)
End Function
Public Function GetEngineSize() As Integer
Return (EngineSize)
End Function
Public Function GetPurchasePrice() As Decimal
Return (PurchasePrice)
End Function
End Classimport java.util.Date;
class Car
{
private String vehicleID;
private String registration;
private Date dateOfRegistration;
private int engineSize;
private float purchasePrice;
public Car(String n, int e) // constructor
{
vehicleID = n;
registration = "";
dateOfRegistration = new Date();
engineSize = e;
purchasePrice = 0;
}
public void setPurchasePrice(float p)
{
purchasePrice = p;
}
public void setRegistration(String r)
{
registration = r;
}
public void setDateOfRegistration(Date d)
{
dateOfRegistration = d;
}
public String getVehicleID()
{
return(vehicleID);
}
public String getRegistration()
{
return(registration);
}
public Date getDateOfRegistration()
{
return(dateOfRegistration);
}
public int getEngineSize()
{
return(engineSize);
}
public float getPurchasePrice()
{
return(purchasePrice);
}
}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.Module Module1
Class Car
Public Property VehicleID() As String
Public Property Registration() As String = ""
Public Property DateOfRegistration() As Date = #1/1/1900#
Public Property EngineSize() As Integer
Public Property PurchasePrice() As Decimal = 0.0
Public Sub New(ByVal n As String, ByVal e As String)
VehicleID = n
EngineSize = e
End Sub
End Class
' Getters and setters come automatically with every property.
' If some properties must be read-only, declare the getters explicitly:
Class Car
Private _VehicleID As String
Public ReadOnly Property VehicleID() As String
Get
Return _VehicleID
End Get
End Property
Public Property Registration() As String
Public Property DateOfRegistration() As Date
Private _EngineSize As Integer
Public ReadOnly Property EngineSize() As Integer
Get
Return _EngineSize
End Get
End Property
Public Property PurchasePrice() As Decimal
Public Sub New(ByVal n As String, ByVal e As String)
_VehicleID = n
Registration = ""
DateOfRegistration = #1/1/1900#
_EngineSize = e
PurchasePrice = 0.0
End Sub
End ClassJava 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)Dim ThisCar As New Car("ABC1234", 2500)Car thisCar = new 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
ThisCar.SetPurchasePrice(12000) ThisCar.PurchasePrice = 12000 ' using properties Console.WriteLine(ThisCar.GetVehicleID()) Console.WriteLine(ThisCar.VehicleID) ' using properties
thisCar.setPurchasePrice(12000); System.out.print(thisCar.getVehicleID());
🚗 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
Car class definition into your program editor and write a simple program to test that each method works.- Design a class
Companyand draw a class diagram. - Write program code to declare the class. Company name and email address are to be set by the constructor and will never be changed.
- 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)
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.
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 | |
|---|---|
| Book | CD |
| 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 borrower | The 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.
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.
⌨️ 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 = gClass LibraryItem ' the base class definition
Private Title As String
Private Author_Artist As String
Private ItemID As Integer
Private OnLoan As Boolean = False
Private DueDate As Date = Today
Sub Create(ByVal t As String, ByVal a As String, ByVal i As Integer)
Title = t
Author_Artist = a
ItemID = i
End Sub
Public Function GetTitle() As String
Return (Title)
End Function
' other Get methods go here
Public Sub Borrowing()
OnLoan = True
DueDate = DateAdd(DateInterval.Day, 21, Today()) ' 3 weeks from today
End Sub
Public Sub Returning()
OnLoan = False
End Sub
Sub PrintDetails()
Console.WriteLine(Title & "; " & ItemID & "; " & OnLoan & "; " & DueDate)
End Sub
End Class
Class Book ' a subclass definition
Inherits LibraryItem ' Inherits is the first statement
Private IsRequested As Boolean = False
Public Sub SetIsRequested()
IsRequested = True
End Sub
End Class
Class CD
Inherits LibraryItem
Private Genre As String = ""
Public Function GetGenre() As String
Return (Genre)
End Function
Public Sub SetGenre(ByVal g As String)
Genre = g
End Sub
End Classimport java.util.Date;
class LibraryItem
{
private String title;
private String author_Artist;
private int itemID;
private Boolean onLoan;
private Date dueDate;
public LibraryItem(String t, String a, int i) // constructor
{
title = t;
author_Artist = a;
itemID = i;
onLoan = false;
dueDate = new Date();
}
public String GetTitle()
{
return(title);
}
// other Get methods go here
public void borrowing()
{
onLoan = true;
//dueDate = dueDate + 21; (see the note: Date needs a Calendar to add days)
}
public void returning()
{
onLoan = false;
}
public void printDetails()
{
System.out.print(title + " ; " + author_Artist + " ; ");
System.out.println(itemID + " ; " + onLoan + " ; " + dueDate);
}
}
class Book extends LibraryItem
{
private Boolean isRequested;
private int requestedBy;
public Book(String t, String a, int i) // constructor
{
super(t, a, i);
isRequested = false;
requestedBy = 0;
}
public Boolean getIsRequested()
{
return(isRequested);
}
public void setIsRequested()
{
isRequested = true;
}
}
class CD extends LibraryItem
{
private String genre;
public CD(String t, String a, int i) // constructor
{
super(t, a, i);
genre = "";
}
public String getGenre()
{
return(genre);
}
public void setGenre(String g)
{
genre = g;
}
}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 = gClass LibraryItem
Public Property Title() As String
Public Property Author_Artist As String
Public Property ItemID As Integer
Public Property OnLoan As Boolean = False
Public Property DueDate As Date = Today
Sub Create(ByVal t As String, ByVal a As String, ByVal i As Integer)
Title = t
Author_Artist = a
ItemID = i
End Sub
Public Sub Borrowing()
OnLoan = True
DueDate = DateAdd(DateInterval.Day, 21, Today()) ' 3 weeks from today
End Sub
Public Sub Returning()
_OnLoan = False ' _OnLoan is the hidden field behind the OnLoan property
End Sub
Public Sub PrintDetails()
Console.WriteLine(Title & "; " & ItemID & "; " & OnLoan & "; " & DueDate)
End Sub
End Class
Class Book
Inherits LibraryItem
Public Property IsRequested() = False
End Class
Class CD
Inherits LibraryItem
Public Property Genre() As String
End ClassInstantiating 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)
Dim ThisBook As New Book() Dim ThisCD As New CD() ThisBook.Create(Title, Author, ItemID) ThisCD.Create(Title, Artist, ItemID)
Book thisBook = new Book("Computing", "Sylvia", 1234);
CD thisCD = new CD("Let it be", "Beatles", 2345);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
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()) 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.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.
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)' in base class, add the keyword Overridable
' to the method to be redefined
Overridable Sub PrintDetails()
' in subclass, add the redefined method:
Public Overrides Sub PrintDetails()
Console.WriteLine("Book Details")
MyBase.PrintDetails() ' calls the base class method with the same name
Console.WriteLine(IsRequested)
End Sub@Override
public void printDetails()
{
System.out.println("Book Details");
super.printDetails(); // calls the base class method with the same name
System.out.println(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
PrintDetails methods for the Book class and the CD class. Test your code.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()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.
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.__BorrowerIDRequestedBy, 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.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.__RequestedByFigure 27.06 Library system menu
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.
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.
| Language | How memory is reclaimed |
|---|---|
| Python | Memory 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.NET | A garbage collector automatically reclaims memory from objects that are no longer referred to by the running program. |
| Java | The 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.
The filled diamond sits at the containing class. The 1 and * say that one car has many wheels.
🎮 Is-a or has-a?
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.
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()Class Course
Private CourseTitle As String
Private MaxStudents As Integer
Private NumberOfLessons As Integer = 0
Private CourseLesson(50) As Lesson
Private CourseAssessment As Assessment
Public Sub Create(ByVal t As String, ByVal m As Integer)
CourseTitle = t
MaxStudents = m
End Sub
Sub AddLesson(ByVal t As String, ByVal d As Integer, ByVal r As Boolean)
NumberOfLessons = NumberOfLessons + 1
CourseLesson(NumberOfLessons) = New Lesson
CourseLesson(NumberOfLessons).Create(t, d, r)
End Sub
Public Sub AddAssessment(ByVal t As String, ByVal m As Integer)
CourseAssessment = New Assessment
CourseAssessment.Create(t, m)
End Sub
Public Sub OutputCourseDetails()
Console.WriteLine(CourseTitle & "Maximum number: " & MaxStudents)
For i = 1 To NumberOfLessons
CourseLesson(i).OutputLessonDetails()
Next
End Sub
End Classclass Course
{
private String courseTitle;
private int maxStudents;
private int numberOfLessons;
private Lesson[] courseLesson;
private Assessment courseAssessment;
public Course(String t, int m) // sets up a new course
{
courseTitle = t;
maxStudents = m;
numberOfLessons = 0;
courseLesson = new Lesson[50];
//courseAssessment = new Assessment();
}
public void addLesson(String t, int d, Boolean r)
{
courseLesson[numberOfLessons] = new Lesson(t, d, r); // indexes 0 to 49
numberOfLessons = numberOfLessons + 1;
}
public void addAssessment(String t, int m)
{
courseAssessment = new Assessment(t, m);
}
public void outputCourseDetails()
{
System.out.println(courseTitle + " - Maximum number of students: " + maxStudents);
for (int i = 0; i < numberOfLessons; i++)
{
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()Dim MyCourse As New Course
MyCourse.Create("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()
Console.ReadLine()Course myCourse = new 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
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.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 minutesThe 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.
- 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.
BankAccount class consists of: attributes AccountHolderName and IBAN (International Bank Account Number); methods CreateNewAccount, SetAccountHolderName, GetAccountHolderName and GetIBAN.
+-------------+
| BankAccount |
+-------------+
▲
+--------+---------+
| |
+-----------------+ +----------------+
| PersonalAccount | | SavingsAccount |
+-----------------+ +----------------+ - 1 mark:
BankAccountas 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
ENDCLASSclass BankAccount:
def __init__(self, Name, Number): # CreateNewAccount
self.__AccountHolderName = Name
self.__IBAN = Number
def SetAccountHolderName(self, Name):
self.__AccountHolderName = Name
def GetAccountHolderName(self):
return self.__AccountHolderName
def GetIBAN(self):
return self.__IBAN- Class header.
- Both attributes declared private.
- Constructor (
CreateNewAccount) with two parameters that sets both attributes. SetAccountHolderNamewith a parameter.- Both getters returning the attribute.
- Attributes:
MonthlyFee : CURRENCY,OverdraftLimit : CURRENCY(andBalance : 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(andBalance : CURRENCY). - Methods:
SetInterestRate()/GetInterestRate(),AddInterest()(balance × rate), a constructor, and a withdraw method that refuses any withdrawal that would make the balance negative.
Encapsulation.
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)Dim NewCustomer As New ContractTicketHolder("A. Smith", "xyz@abc.xx", 10.0)ContractTicketHolder newCustomer = new ContractTicketHolder("A. Smith", "xyz@abc.xx", 10.0f);- Identifier
NewCustomerassigned / declared. - Calls the
ContractTicketHolderconstructor (usingNewin VB.NET and Java). - The three values in the right order and of the right types.
Queue and Node. The class diagrams are as follows:Containment (aggregation): a QueueClass object contains NodeClass objects. It is not inheritance: a node is not a kind of queue.
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 = 0The 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
Borrowingadded 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 + 21is not valid Java for aDate(the book comments it out). A real version would useCalendar.add(Calendar.DATE, 21)orLocalDate.plusWeeks(3). - Python
Course:self.__CourseAssessment = Assessmentstored the class itself, not an object, so it is nowNone.print(...OutputLessonDetails())would also printNoneafter each lesson, so theprintis removed. - Java
Course: lessons were stored from index 1 of a 50-element array (the 50th lesson would crash), and the loopi = 1; i < numberOfLessonsskipped the last lesson. Both now count from 0. - Task 27.05 says
LoanItem.BorrowerID; it meansLibraryItem.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