B3.2.3 · Fundamentals of OOP for multiple classes (HL)
Abstraction and abstract classes
Writing down what every subclass must be able to do, without pretending to know how any of them will do it.
Explainer
Lansdown Vehicle Hire rents out cars, vans, motorcycles and trucks. Every booking screen has to show two things: what it costs per day, and what category of licence the driver needs to be allowed to drive it. Neither has a generic answer. A motorcycle is category A, a car or van is B, a truck is C, and the daily rates are nowhere near each other. Inventing a default would not merely be untidy: it would quote the wrong price, or hand a truck to someone licensed only for a car.
Abstraction is the practice of exposing only the essential features of something while keeping the details of how it works out of sight. In OOP it means designing around what an object can be asked to do, rather than around how any particular object does it.
An abstract class is a class that cannot be instantiated on its own. It exists to be inherited from. It may declare abstract methods, which have a name and a signature but no body, and which every concrete subclass is obliged to implement.
Together these give the hire firm exactly what it needs: a written guarantee that every vehicle type, including ones nobody has added yet, can answer "what do you cost?" and "what licence do you need?", without the booking screen knowing a single thing about cars, motorcycles or trucks.
Explainer
The test for when an abstract class is the right choice
Not every parent class should be abstract. The deciding question is short, and it is the one examiners reward:
Every subclass must have this method, and there is no sensible default implementation you could write for it. If you can write a reasonable default, use a normal class and let subclasses override it. If any default you could write would be a guess, make the method abstract and force each subclass to supply its own.
Applied at Lansdown: licence_category() has no defensible default, so it is abstract. hire_summary() is the same sentence for every vehicle, so it is written once as a normal method and inherited. One abstract class can hold both kinds at once, which is a point worth stating explicitly in an answer.
Worked example
The contract, in code
daily_rate()
Declared, never implemented here. Every concrete subclass must supply its own, because a rate that suits a motorcycle would badly undercharge a truck.
licence_category()
Same reasoning, with a legal edge: guessing this wrong hands a vehicle to a driver who is not entitled to drive it.
hire_summary()
Written once and inherited by all. It calls the two methods above without knowing or caring which subclass will answer.
What is from abc import ABC, abstractmethod? Python has no abstract keyword, so unlike Java it cannot mark a class abstract in the language itself. The tools come from a module in the standard library instead, called abc, short for Abstract Base Class. Nothing needs installing, it ships with Python.
The line brings in two separate things. ABC is a class to inherit from, and inheriting it is what makes your own class abstract. abstractmethod is a decorator, written on the line above a method, marking that method as one every concrete subclass is obliged to implement. You need both: ABC alone with no abstract methods would still be perfectly instantiable.
from abc import ABC, abstractmethod class Vehicle(ABC): # inheriting ABC makes it abstract def __init__(self, reg, model): self.reg = reg self.model = model @abstractmethod def daily_rate(self): # no body: subclasses MUST write it pass @abstractmethod def licence_category(self): pass def hire_summary(self): # CONCRETE: shared by every vehicle return f"{self.reg} {self.model}: {self.daily_rate()} per day, licence {self.licence_category()}" class Car(Vehicle): def daily_rate(self): return 45 def licence_category(self): return "B"
abstract class Vehicle { // the abstract keyword does it here protected String reg; protected String model; public abstract int dailyRate(); // no body, ends with a semicolon public abstract String licenceCategory(); public String hireSummary() { // CONCRETE: shared by every vehicle return reg + " " + model + ": " + dailyRate() + " per day"; } } class Car extends Vehicle { @Override public int dailyRate() { return 45; } @Override public String licenceCategory() { return "B"; } }
Python has no abstract keyword. It uses the standard library instead: inherit from ABC and mark the required methods with @abstractmethod. Java has the keyword built into the language, and an abstract method is written with a semicolon where the body would go.
Worked example
The two things an abstract class refuses
This is what makes the contract real rather than a comment. Both of these are errors, caught before any wrong lorry is loaded.
1. You cannot create one directly
# Vehicle is a description, not a thing you can hire v = Vehicle("LV01 XYZ", "generic")
TypeError: Can't instantiate abstract class Vehicle with abstract methods daily_rate, licence_category
2. You cannot half-implement it
# forgets licence_category() class Van(Vehicle): def daily_rate(self): return 65 v = Van("LV22 KLM", "Transit")
TypeError: Can't instantiate abstract class Van with abstract method licence_category
Notice when the second error appears: not when the incomplete class is defined, but the moment something tries to create one. The contract is checked at instantiation, which is still long before that van could reach a booking screen.
Explainer
Why this makes code more modular
The first elaboration bullet asks specifically about modular code fragments, so it is worth being precise about what changes.
# the booking screen: it knows nothing about cars, motorcycles or trucks def show_fleet(vehicles): for v in vehicles: print(v.hire_summary())
This function depends on the abstract class alone, never on any concrete vehicle type. That has three consequences worth naming in an answer:
The fragments can be written separately. One developer builds the booking screen while another writes Truck. Neither needs the other's code to exist, only the agreed contract.
They can be changed independently. Changing how a truck works out its daily rate cannot break the booking screen, because the screen only ever relied on the method existing and returning a value.
A missing piece fails loudly and early. Without the abstract class, a vehicle that forgot licence_category() would only be discovered when a customer reached the checkout. With it, the object cannot be created at all.
Explainer
Abstract class, or an ordinary parent class?
Both give inheritance and shared code. Only one enforces anything.
Real example: a Vehicle whose licence_category() returns "B" as a fallback, because most of the fleet is cars and vans.
Advantage
- Simpler, and usable on its own (a plain Vehicle object can be created for quick testing, with no subclass required)
Disadvantage
- A forgotten override fails silently (a new truck class that never defines licence_category() quietly inherits "B", so the system lets a car driver book a truck and nothing in the code complains)
Real example: the Vehicle(ABC) above, where licence_category() has no body at all.
Advantage
- The contract is enforced by the language (a subclass that forgets a required method cannot be instantiated, so the mistake surfaces immediately instead of becoming a wrongly licensed hire)
Disadvantage
- More rigid, and never usable alone (every subclass must implement every abstract method even when one of them has nothing meaningful to say, and the class itself can never be instantiated for testing)
Try it yourself
Try to break the contract
The Lansdown classes, live and editable. Two experiments: uncomment the line that tries to build a bare Vehicle, and the Van class that deliberately forgets licence_category(). Both should be refused.
Running in offline fallback mode. The full Python engine couldn’t load (blocked school network, or no internet), so this console switched automatically to a small hand-written engine. It runs this page’s scenario, including ABC, @abstractmethod and the refusal to instantiate an incomplete class, but it isn’t full Python.
Check your understanding
Vehicle is abstract with one abstract method and one concrete method. Car extends it and implements the abstract method. Which statement is correct?
Common mistake
B3.2.3RecurringCandidates claim an abstract class cannot contain any working code, describing it as a list of empty method names. An abstract class may hold concrete methods, attributes and a constructor; what it cannot do is be instantiated. Answers built on the stricter, wrong version lose marks even where the surrounding reasoning is good.
The second error is choosing abstraction where plain inheritance would do. If a sensible default implementation exists, an ordinary parent class is the better design, and saying so demonstrates the judgment the command term is asking for.
Answer frame that scores: state that it cannot be instantiated → state what subclasses are obliged to provide → note that it may still supply shared concrete code.
What a weak answer looks like: “An abstract class is an empty class containing only method names, which the subclasses then fill in.” The first half is wrong: hire_summary() is real, working, inherited code sitting inside an abstract class.
What it should say: “Vehicle cannot be instantiated. It declares licence_category() as abstract, so every concrete subclass must implement it, while still providing hire_summary() as working code that all vehicles inherit unchanged.” Instantiation, obligation, and shared code all stated.
Revision questions
Past-paper style. Write an answer, then reveal the marking points and mark yourself.
[2]Explain why licence_category() is declared abstract in Vehicle, while hire_summary() is not.
Marking points
评分要点
- licence_category() has no answer that is correct for every vehicle: a car, a van and a minibus each need a different licence, so there is no sensible default and it is declared abstract to force each subclass to supply its own.
- hire_summary() does the same formatting work for every vehicle, so it can be written once as a concrete method in Vehicle and inherited unchanged.
- licence_category() 没有一个对每种车辆都正确的答案:轿车、厢式货车和小巴各需要不同的驾照,所以没有合理的默认值,把它声明为抽象以强制每个子类提供自己的实现。
- hire_summary() 对每种车辆做相同的格式化工作,所以它可以作为 Vehicle 中的一个具体方法写一次,并被原样继承。
[4]Lansdown adds minibuses at 80 per day, requiring a category D1 licence. Construct Minibus so that it satisfies the Vehicle contract.
Marking points
评分要点
- class Minibus(Vehicle): inheriting the shared attributes and hire_summary().
- Its constructor calls super().__init__(...) and sets the daily rate to 80.
- It implements the abstract method: def licence_category(self): return D1.
- Because it provides licence_category(), Minibus is now concrete and satisfies the Vehicle contract, so it can be instantiated and used anywhere a Vehicle is expected.
- class Minibus(Vehicle):,继承共享属性和 hire_summary()。
- 它的构造函数调用 super().__init__(...) 并把每日费率设为 80。
- 它实现抽象方法:def licence_category(self): return D1。
- 因为它提供了 licence_category(),Minibus 现在是具体的并满足 Vehicle 契约,所以它可以被实例化,并用在任何期望 Vehicle 的地方。
[3]A developer proposes replacing the abstract class with an ordinary parent class whose licence_category() returns "B". Evaluate this proposal for the hire firm.
Marking points
评分要点
- An ordinary parent with licence_category() returning B removes the compiler or run-time check that every subclass provides its own.
- A new vehicle type that forgot to override it would silently inherit B, quoting the wrong licence, a serious error for a hire firm.
- The abstract class makes forgetting an error the developer must fix before the code runs.
- Evaluation: the proposal is a poor trade, swapping a guaranteed check for a hidden default; keep the abstract class.
- 一个返回 B 的普通父类 licence_category() 去掉了对每个子类提供自己实现的编译期或运行期检查。
- 一个忘记重写它的新车辆类型会悄悄继承 B,报出错误的驾照,对租赁公司来说是严重的错误。
- 抽象类使遗忘成为开发者在代码运行前必须修复的错误。
- 评价:该提案是一笔糟糕的交易,用一个隐藏的默认值换掉一个有保证的检查;保留抽象类。
Flashcards
Active recall for this statement’s key terms.
Keep this statement’s practice with you
Free account: unlock every revision question and flashcard for B3.2.3, and every other IB CS 2027 statement.