← All lessons  ·  Cambridge 9618 A Level · Unit 20.1 · Paper 4 Practical

Object-Oriented Programming
Home Lesson 6 — A Full Paper 4 Question

Five lessons in one question: a class, a child class, a container and a main program — against the clock, then marked and evidenced exactly as in June.

Home lesson 6 of 6 Language: Python (console) Timed attempt: 65 minutes Allow 90 minutes in total Submit: Evidence Document (.docx)

Start here

How this lesson works

No new content today. This is the dress rehearsal: one Paper 4 question of 32 marks, at exam pace, followed by honest marking and a diagnosis of exactly which lesson to go back to.

  1. Warm up (15 minutes). Do Now, then practise decoding a class design on a small example. Nothing here is timed.
  2. The timed attempt (65 minutes). Start the clock, then write the whole program in your own IDE — IDLE, Thonny or VS Code — as you will in the exam. Paste each part into the Evidence Document as you finish it. No notes, no earlier lessons, no searching.
  3. Mark, diagnose, submit (10 minutes). Self-mark against the mark scheme, find your weakest lesson in the diagnosis table, and submit the Evidence Document on Teams.

Why 65 minutes

Paper 4 is 75 marks in 2 hours 30 minutes — about two minutes per mark. This question is worth 32, so 65 minutes is exam pace. If you cannot do it in one sitting, split it across two home periods: the timed attempt in one, the marking and evidence in the next. Do not split the timed attempt itself.

If the clock runs out, stop writing, note which part you reached in the Evidence Document, and then finish the program untimed. Both numbers are useful — the first tells you about pace, the second about knowledge.


Retrieval · real past-paper question

Do Now — four minutes, no notes

Question 1 is a real Cambridge Paper 3 question. Write your answers in your book, then reveal the mark scheme and mark them.

Questions

  1. Match one description to each Object-Oriented Programming (OOP) term. Write the term and its letter. [4]
    9618 Paper 3, June 2023 (variant 2), Q4 — on paper you draw lines
    OOP termDescription
    EncapsulationA  methods used to return the value of a property
    GettersB  the process of putting data and methods together as a single unit
    PolymorphismC  methods used to update the value of a property
    SettersD  allows methods to be redefined for derived classes
    E  enables the defining of a new class that inherits from a parent class
  2. Car's constructor takes Make and Model. The class Hatchback(Car) has the constructor header def __init__(self, Make, Model, Doors):. Write the first line of its body. [1]
  3. A candidate writes this get method: def GetAge(self, Age): return Age. State two things that are wrong with it. [2]
Mark scheme — mark your own answers
QAcceptedMarks
1One mark for each correct match (Cambridge mark scheme):
Encapsulation — B  ·  Getters — A  ·  Polymorphism — D  ·  Setters — C.
E is not used: it describes inheritance, which is not one of the four terms. A description left over is normal in a matching question — do not force it onto a term.
4
2super().__init__(Make, Model)  //  Car.__init__(self, Make, Model)
Reject: passing Doors to the parent; omitting the parameters.
1
3Any two: it takes a parameter — a get method takes none (beyond self) // it returns the parameter, not the attribute — it should return self.__Age // it does not access the object's attribute at all.2

Q3 is the single most repeated OOP error in Paper 4 — a get method with a parameter appears in the examiner reports of all ten series since 2021. If you lost a mark on it, read Lesson 2 again before you start the clock.


Where this is going

Learning objectives

By the end of this lesson I can…SyllabusPaper
Complete a full Paper 4 OOP question — class, child class, container and main program — at exam pace.20.14
Read a class design accurately: is a or has a; parameter or fixed value; returns or outputs.20.14
Store and use the value a method returns, including one returned through a container.20.14
Evidence a program to Paper 4 rules, and diagnose my lost marks against the lesson that teaches each one.20.14

Exam technique

The first five minutes — read the design, not the code you remember

Examiners keep saying the same thing: the strongest candidates are not the ones who know the most syntax. They are the ones who follow the design they are given. "Data structures differ each year to test if candidates can manipulate them when they are working in a slightly different way" (June 2023). So before you type a single line, spend five minutes with a pen.

  1. Read every part first. Later parts tell you what earlier methods are for. Part (i) of today's question explains why part (g) has to return a value rather than output it.
  2. Label each class. Does another class's name appear in its data type column? Then it has those objects — containment, no brackets. Does the description say "subclass of" or "inherits from"? Then it is a — inheritance, brackets and super().__init__.
  3. Label each attribute P (from a parameter) or F (fixed value). Only the P attributes appear in the constructor header. A child constructor takes the parent's P attributes first, then its own.
  4. Label each method R (returns) or O (outputs). For every R, ask: does it return on every path, and where in the program is the value stored and used?
  5. Work out the expected output from the test data before you write any code. Then you will know immediately when the program is wrong.

Warm-up · untimed

Decode a design — a parcel company

Practise those five steps on a small design that is not today's question. Do not write the code; just answer the checkpoints.

Class design

ParcelData typeDescription
ParcelIDSTRINGFrom a parameter
WeightINTEGERFrom a parameter
DeliveredBOOLEANAlways FALSE when created
GetParcelID()Returns ParcelID
ExpressParcel — a subclass of ParcelData typeDescription
DeadlineSTRINGFrom a parameter
PriorityINTEGERAlways 1 when created
VanData typeDescription
RegistrationSTRINGFrom a parameter
LoadARRAY[0:19] OF ParcelTwenty empty elements
ParcelCountINTEGERAlways 0 when created
LoadParcel(NewParcel)Stores NewParcel in the next free element if there is space, and returns TRUE; otherwise returns FALSE
FindParcel(SearchID)Returns the index of the stored parcel whose ID is SearchID, or -1 if there is none
Checkpoint 1

How many parameters, after self, does the constructor of ExpressParcel take?

Checkpoint 2

Load is declared as ARRAY[0:19] OF Parcel. Which of these can be passed to LoadParcel()?

Checkpoint 3

Which version of FindParcel follows the design?


Assessed work · timed

The question — bike hire  [32 marks]

Exam clock
65:00
Read the whole question first — that is part of the 65 minutes.

Question 1

A city runs a bike hire scheme from several hire stations. A program is to be written using Object-Oriented Programming. There are two kinds of bike: ordinary bikes, and electric bikes, which also have a battery. Each hire station stores up to 10 bikes.

The class Bike stores data about one bike.

Bike
BikeID : STRINGthe bike's unique ID, for example "B101"
Model : STRINGthe model of the bike
HireRate : INTEGERthe cost of hiring the bike for one hour
HoursHired : INTEGERthe total number of hours the bike has been hired for, initialised to 0
Available : BOOLEANTRUE if the bike can be hired, initialised to TRUE
Constructor()takes the bike ID, model and hire rate as parameters, and initialises all the attributes
GetBikeID()returns the bike ID
GetAvailable()returns the value of Available
Hire(Hours)if the bike is available: adds Hours to HoursHired, sets Available to FALSE and returns the cost, Hours multiplied by HireRate.
If the bike is not available, returns -1

The class EBike inherits from Bike.

EBike
BatteryLevel : INTEGERthe percentage of charge in the battery
Constructor()takes the bike ID, model, hire rate and battery level as parameters. Calls the parent constructor and initialises BatteryLevel
Hire(Hours)overrides the parent method.
If BatteryLevel is less than 20, returns -1.
Otherwise calls the parent's Hire() method. If the value it returns is not -1, reduces BatteryLevel by 10 for every hour hired. Returns the value returned by the parent's method.

The class HireStation stores the bikes at one station.

HireStation
StationName : STRINGthe name of the station
Bikes : ARRAY[0:9] OF Bikethe bikes at the station, initially empty
NumberOfBikes : INTEGERthe number of bikes stored, initialised to 0
Constructor()takes the station name as a parameter and initialises all the attributes
AddBike(NewBike)if there is space, stores NewBike in the next free element of Bikes, adds 1 to NumberOfBikes and returns TRUE. Otherwise returns FALSE
HireBike(BikeID, Hours)searches the stored bikes for the bike with the ID BikeID. If it is found, hires it for Hours hours using its Hire() method and returns the value that Hire() returns. If no stored bike has that ID, returns -1
CountAvailable()returns the number of stored bikes that are available

(a) Write program code to declare the class Bike and its constructor. Use your programming language's appropriate constructor. All attributes must be private. If you are writing in Python, include attribute declarations using comments. [4]

(b) Write program code for the methods GetBikeID() and GetAvailable(). [2]

(c) Write program code for the method Hire() in the class Bike. [4]

(d) Write program code to declare the class EBike and its constructor. [3]

(e) Write program code for the method Hire() in the class EBike. [4]

(f) Write program code to declare the class HireStation, its constructor and the method AddBike(). [4]

(g) Write program code for the method HireBike(). [4]

(h) Write program code for the method CountAvailable(). [2]

(i) Write the main program. It must:

  • create a HireStation object with the name "Harbour Road"
  • create the five bikes below, each as the class shown, and add each one to the station using AddBike()
  • for each hire request below, in order: call HireBike(), store the value it returns, and output either
    <BikeID> hired for <Hours> hours, cost <value>  or  <BikeID> cannot be hired if the value is -1
  • output Bikes available: followed by the value returned by CountAvailable(). [3]
ClassBikeIDModelRateBattery
Bike"B101""City Cruiser"4—
Bike"B102""Trail Runner"5—
EBike"E201""Volt 500"985
EBike"E202""Volt 500"915
Bike"B103""City Cruiser"4—
RequestBikeIDHours
1"B102"3
2"E201"2
3"E202"1
4"B102"2
5"B777"1

(j) Test your program. Take a screenshot showing the output. [2]

Stuck on one part for more than five minutes? One hint per part

In the exam you would move on, and parts are marked independently — so move on here too. If you open a hint, write "hint used" beside that part in the Evidence Document. Honesty is what makes the diagnosis work.

  • (a) Three P attributes, two F attributes. Only the P ones are in the constructor header.
  • (b) Neither method takes a parameter beyond self.
  • (c) Two paths, two returns. Change the attributes before the return — nothing after a return runs.
  • (d) Four parameters. The next line passes three of them to super().__init__.
  • (e) Cost = super().Hire(Hours) on a line of its own — then test Cost.
  • (f) Lesson 4's Club, with different names and ten elements.
  • (g) Lesson 3's linear search, inside the class. Return what the bike's Hire() returns; return -1 only after the loop.
  • (h) Loop to NumberOfBikes, not to 10. Ask each bike with GetAvailable().
  • (i) Station.AddBike(EBike("E201", "Volt 500", 9, 85)) creates the object and passes it in one line.
Python 3 — scratch pad Python is loading…
Output
Press Run to execute.

Use your own IDE if you can — the exam does. This runner is here if you have nothing else, and it saves your code in this browser as you type. Paste into the Evidence Document from whichever you used.

Extension — only if you finish inside the 65 minutes

Add a get method GetBatteryLevel() to EBike. In the main program, keep the E201 object in a variable before you add it to the station, and after the other output, use it to output E201 battery: followed by its battery level.

Then explain in one sentence, in the Evidence Document, why the station's copy of E201 and your variable show the same battery level.

Check your output — open only after the clock has stopped
B102 hired for 3 hours, cost 15
E201 hired for 2 hours, cost 18
E202 cannot be hired
B102 cannot be hired
B777 cannot be hired
Bikes available: 3

With the extension, a seventh line: E201 battery: 65. If yours differs, the difference tells you where to look:

You seeLook at
E202 hired for 1 hours, cost 9 and Bikes available: 2(e) — the battery check is missing. E202 has only 15%.
B102 hired for 2 hours, cost 10(c) — Hire() is not setting Available to False, so B102 is hired twice.
Every request cannot be hired(g) — return -1 is inside the loop, so only the first bike, B101, is ever checked. None of the requests is for B101.
'NoneType' object has no attribute …(g) or (h) — a loop runs to 10 instead of to NumberOfBikes. Only five bikes are stored.
AttributeError: … '_Bike__…'(d) — the parent's constructor is not being called.
AttributeError: … '_EBike__Available' or similar(e) — the child is reaching into the parent's private attributes. Let super().Hire(Hours) do that work.
cost None on a line(c), (e) or (g) — a path through one of them has no return, or a value returned is not passed on.

Self-marking

Mark scheme — open it only after the clock has stopped

Mark strictly. A mark point is a behaviour: if your code does not do it, you do not get it, however close it looks. Record each point in the Evidence Document.

Reveal the mark scheme [32 marks]
PointDescriptionMarks
(a) MP1BikeID, Model and HireRate declared private, with their data types evidenced.1
(a) MP2Constructor takes exactly three parameters and assigns them to those attributes — attribute on the left.1
(a) MP3HoursHired private, typed, assigned 0 in the constructor.1
(a) MP4Available private, typed, assigned True in the constructor.1
(b) MP5GetBikeID() — no parameter, returns BikeID.1
(b) MP6GetAvailable() — no parameter, returns Available.1
(c) MP7Hire() takes one parameter and tests whether the bike is available.1
(c) MP8…when available, adds Hours to HoursHired and sets Available to False…1
(c) MP9…and returns Hours * HireRate.1
(c) MP10Returns -1 when not available.1
(d) MP11Class header shows inheritance from Bike.1
(d) MP12Constructor takes four parameters and calls the parent constructor with the first three.1
(d) MP13BatteryLevel private, typed, assigned from the parameter.1
(e) MP14Hire() defined in EBike with one parameter, returning -1 when BatteryLevel < 20.1
(e) MP15Otherwise calls the parent's Hire(Hours) and stores the value it returns.1
(e) MP16Reduces BatteryLevel by 10 * Hours only when that value is not -1.1
(e) MP17Returns the stored value.1
(f) MP18class HireStation: — no parent — with StationName private, from the one parameter.1
(f) MP19Bikes a private array of 10 elements typed as Bike, and NumberOfBikes private and set to 0.1
(f) MP20AddBike() checks for space, stores at index NumberOfBikes, then adds 1.1
(f) MP21Returns True / False on every path.1
(g) MP22Loops over the stored bikes only.1
(g) MP23Compares each bike's GetBikeID() with the parameter.1
(g) MP24Calls Hire(Hours) on the matching bike and returns the value it returns.1
(g) MP25Returns -1 only after the loop.1
(h) MP26Count set to 0, loop over stored bikes, GetAvailable() tested and count incremented.1
(h) MP27Count returned after the loop.1
(i) MP28Station created; the five bikes created as the right classes with the test data, each added with AddBike().1
(i) MP29Each request made with HireBike(); value returned stored, tested against -1, and the right message output in each case.1
(i) MP30Bikes available: output with the value returned by CountAvailable().1
(j) MP31Screenshot shows the program running with the test data.1
(j) MP32Output matches the six expected lines.1

Accepted alternatives

  • MP12 and MP15: Bike.__init__(self, BikeID, Model, HireRate) and Bike.Hire(self, Hours).
  • MP14: testing for the battery being 20 or more first, with the -1 in the else.
  • MP24–MP25: a while loop with a found flag, provided the value returned by Hire() is the value returned by HireBike().
  • MP28: creating each bike in a variable first, or a loop over a list of the test data.

Not credited, however good the code looks

  • HoursHired, Available or a fixed value taken as a constructor parameter — MP2 lost, as well as the point for that attribute.
  • print(Hours * self.__HireRate) in place of return — MP9 lost, and everything that relies on the value fails with it.
  • Changing Available after the return line — MP8 lost. That line never runs.
  • super().Hire(Hours) called inside an if condition and again on the next line — MP15 lost. The bike is hired twice: the second call finds it unavailable and returns -1.
  • self.__Available or self.__HireRate anywhere in EBike — MP15 lost. They are private to Bike.
  • class HireStation(Bike): — MP18 lost. Bike is in the data type column: a station has bikes.
  • HireBike() that outputs a message instead of returning — MP24 lost. The design says returns, and part (i) depends on it.
  • Calling HireBike() without storing what it returns, then calling it again to print the cost — MP29 lost, and the second call reports the bike as unavailable.
  • Code pasted as an image — no code marks at all.
A full-mark answer (WAGOLL) — read it after marking, line by line
Python — Paper 4
# 1(a)
class Bike:
    def __init__(self, BikeID, Model, HireRate):
        self.__BikeID = BikeID       # STRING
        self.__Model = Model         # STRING
        self.__HireRate = HireRate   # INTEGER
        self.__HoursHired = 0        # INTEGER
        self.__Available = True      # BOOLEAN

    # 1(b)
    def GetBikeID(self):
        return self.__BikeID

    def GetAvailable(self):
        return self.__Available

    # 1(c)
    def Hire(self, Hours):
        if self.__Available == True:
            self.__HoursHired = self.__HoursHired + Hours
            self.__Available = False
            return Hours * self.__HireRate
        else:
            return -1


# 1(d) an EBike IS A Bike
class EBike(Bike):
    def __init__(self, BikeID, Model, HireRate, BatteryLevel):
        super().__init__(BikeID, Model, HireRate)
        self.__BatteryLevel = BatteryLevel     # INTEGER

    # 1(e) overrides Bike.Hire
    def Hire(self, Hours):
        if self.__BatteryLevel < 20:
            return -1
        Cost = super().Hire(Hours)             # store what the parent's method returns
        if Cost != -1:
            self.__BatteryLevel = self.__BatteryLevel - 10 * Hours
        return Cost


# 1(f) a HireStation HAS bikes — no brackets
class HireStation:
    def __init__(self, StationName):
        self.__StationName = StationName       # STRING
        self.__Bikes = [None] * 10             # ARRAY[0:9] OF Bike
        self.__NumberOfBikes = 0               # INTEGER

    def AddBike(self, NewBike):
        if self.__NumberOfBikes < 10:
            self.__Bikes[self.__NumberOfBikes] = NewBike
            self.__NumberOfBikes = self.__NumberOfBikes + 1
            return True
        else:
            return False

    # 1(g)
    def HireBike(self, BikeID, Hours):
        for Index in range(0, self.__NumberOfBikes):
            if self.__Bikes[Index].GetBikeID() == BikeID:
                return self.__Bikes[Index].Hire(Hours)
        return -1

    # 1(h)
    def CountAvailable(self):
        Count = 0
        for Index in range(0, self.__NumberOfBikes):
            if self.__Bikes[Index].GetAvailable() == True:
                Count = Count + 1
        return Count


# 1(i) main program
Station = HireStation("Harbour Road")
Added = Station.AddBike(Bike("B101", "City Cruiser", 4))
Added = Station.AddBike(Bike("B102", "Trail Runner", 5))
Added = Station.AddBike(EBike("E201", "Volt 500", 9, 85))
Added = Station.AddBike(EBike("E202", "Volt 500", 9, 15))
Added = Station.AddBike(Bike("B103", "City Cruiser", 4))

Requests = [["B102", 3], ["E201", 2], ["E202", 1], ["B102", 2], ["B777", 1]]
for Request in Requests:
    Cost = Station.HireBike(Request[0], Request[1])
    if Cost == -1:
        print(Request[0] + " cannot be hired")
    else:
        print(Request[0] + " hired for " + str(Request[1]) + " hours, cost " + str(Cost))

print("Bikes available: " + str(Station.CountAvailable()))
  • Polymorphism is doing the work in (g). HireBike calls Hire() on whatever is in the array. For E201 and E202 that is EBike's version, with the battery check; for the others it is Bike's. The station never asks which class a bike is.
  • The value returned travels three levels. Bike.Hire returns the cost to EBike.Hire, which stores it and returns it to HireStation.HireBike, which returns it to the main program, which stores it and tests it. Break any link and the output fails — that chain is exactly what examiners mean by "using what a call returns".
  • (i) uses Added = … even though nothing is tested here. Keeping what a method returns is a habit; in a question that asked for a club-full message, it would be the mark.

After the marking

Diagnose — which lesson do your lost marks point to?

Find every mark point you lost in this table. The lesson that appears most often is the one to redo — the whole page, Exam Conditions tier included — before the mock.

Mark pointsWhat they testGo back to
MP1–MP4, MP13Private attributes, data types, fixed values vs parametersLesson 1 — Classes, Attributes and the Constructor
MP5–MP10Get methods without parameters; returning, not outputtingLesson 2 — Get and Set Methods
MP22–MP30Looping over objects, searching, storing what a call returnsLesson 3 — Using Objects
MP18–MP21Has a: an array of objects inside a class, with a counterLesson 4 — Containment or Inheritance?
MP11–MP12, MP14–MP17Is a: the parent constructor call, overriding, the parent's methodLesson 5 — Inheritance
MP31–MP32Evidence: test data and output in the screenshotThe marking rules on page 1 of every Evidence Document

Pace or knowledge?

Compare the part you reached when the clock stopped with your final mark. Lots of marks but an unfinished question means pace: practise the Exam Conditions tiers with a timer. An unfinished question with marks lost in the parts you did write means knowledge: redo the lesson the table points to. Many people have a bit of both — write down which matters more for you.


Hand-in

What to submit on Teams

One file: the Evidence Document for this lesson, saved as .docx and named Surname Initial — OOP Home Lesson 6.docx.

Checklist before you upload

  • Your name and class are at the top of the document, with your start and finish times and the part you reached at 65 minutes.
  • Parts (a) to (i) are each pasted as text into their own box, with the indentation intact. Any part where you opened a hint is marked hint used.
  • The screenshot for (j) shows the program running with the test data, and the output is not cut off.
  • Your self-marked total out of 32 is filled in, and the diagnosis names the lesson you will go back to.
  • You are submitting the document only — not a zip, not the .py file.

Your next taught lesson opens by going through the most common lost marks across the class. If you ran out of time, submit what you have — the unfinished parts are useful evidence too.

What comes next

That completes the OOP arc. In the exam, an OOP question usually sits alongside one on file handling and one on an abstract data type — and the OOP question itself often loads its objects from a text file. Those topics are next on the practical strand, and everything from these six lessons carries straight into them.

Cambridge 9618 A Level Computer Science  ·  20.1 Programming Paradigms (OOP)  ·  Home Lesson 6 of 6  ·  Paper 4 Practical  ·  United School International