B3.2.1 · Fundamentals of OOP for multiple classes (HL)
Inheritance and code reusability
Writing the shared part once, then letting specialised classes take it and build on top of it.
Explainer
Ravensworth Climbing Centre logs every route on its walls. A boulder problem and a roped route are genuinely different things: one records the depth of matting underneath it, the other records how many bolts it has and whether it runs over more than one pitch. But both have a route code, a grade, the name of whoever set it, and a count of how many times it has been climbed. Writing two separate classes means writing that shared half twice, and fixing every future bug in it twice.
Inheritance lets a new class start from an existing one, automatically receiving its attributes and methods, so that only the differences have to be written. The shared code lives in one place and is reused rather than repeated.
The class being inherited from is the parent class (also called the superclass or base class). The class doing the inheriting is the child class (also called the subclass or derived class). The relationship is hierarchical: children sit below their parent, and a child can itself be the parent of another class further down.
The test for whether inheritance is the right tool is the is-a question. A boulder problem is a route, so BoulderRoute inheriting from Route is sound. A climbing wall is not a route, it has routes, so that relationship is not inheritance at all.
Worked example
One parent, two children
Everything in the parent is available to both children without being rewritten. The greyed italic rows below are not typed out again in the child classes, they arrive automatically.
The hollow triangle always points at the parent.
Notice what is not italic in the children: matDepth and bolts are the genuinely new parts, and describe() appears in both children in normal type because each one replaces the parent's version with its own. That replacement is called overriding.
Explainer
Reusing, and then extending
Inheritance is not only about taking a copy. A child normally does three things: it accepts what it wants unchanged, it adds what is new, and it replaces what needs to behave differently.
# the shared half, written once class Route: def __init__(self, code, grade, setter): self.code = code self.grade = grade self._times_climbed = 0 # protected by convention self.__inspection_log = "clear" # private to Route def log_climb(self): self._times_climbed += 1 def describe(self): return f"{self.code} ({self.grade})" class BoulderRoute(Route): # inherits everything above def __init__(self, code, grade, setter, mat_depth): super().__init__(code, grade, setter) # run the parent's setup first self.mat_depth = mat_depth # ADD what is new def describe(self): # REPLACE the parent's version return super().describe() + f", {self.mat_depth}cm matting"
// the shared half, written once public class Route { public String code; public String grade; protected int timesClimbed; // reachable by subclasses private String inspectionLog; // NOT reachable by subclasses public void logClimb() { timesClimbed++; } public String describe() { return code + " (" + grade + ")"; } } public class BoulderRoute extends Route { // inherits everything above public int matDepth; public BoulderRoute(String code, String grade, int matDepth) { super(code, grade); // run the parent's constructor first this.matDepth = matDepth; // ADD what is new } @Override public String describe() { // REPLACE the parent's version return super.describe() + ", " + matDepth + "cm matting"; } }
Python marks inheritance with parentheses after the class name; Java uses the extends keyword. Both call the parent's constructor first through super, because the parent's own setup has to finish before the child adds anything of its own.
log_climb() is written once, in Route. Both children get it, and a third route type added next year gets it too, with no new code. If the counting rule ever changes, it changes in exactly one place rather than in every route class that exists.
Worked example
Where a method is actually found
Calling boulder.log_climb() works even though BoulderRoute never defines it. Step through what the interpreter does to find it.
Step 1. The call is made on a child object. boulder was built from BoulderRoute, so that is where the search starts.
Step 2. Look on the child class first. BoulderRoute defines __init__ and describe, but no log_climb. The search does not stop here, it moves up.
Step 3. Move up to the parent. Route does define log_climb, so the search stops here. This is the whole mechanism behind reuse: nothing was copied into the child, the lookup simply continues upwards.
Step 4. Run it, with self still the child object. The method lives on Route, but it operates on the boulder object, so it is that object's own counter that goes up. Had BoulderRoute defined its own log_climb, the search would have stopped at Step 2 and the parent's version would never have run: that is what overriding means.
Explainer
What a child can and cannot reach
Inheriting a class does not mean inheriting permission to touch all of it. Each access modifier answers the question differently, and this is the part of the statement most often examined. Click through all four.
The child class
Unrelated outside code
Private members are inherited, but not accessible. A BoulderRoute object really does carry the parent's private inspection log inside it, and the parent's own public methods can still read it. What the child class cannot do is write code that touches it directly. Inherited and reachable are two different things, and saying "private members are not inherited" is the version that loses the mark.
Try it yourself
Test the boundary yourself
The Ravensworth classes, live and editable. The last line is commented out on purpose: uncomment it to watch a child class get refused access to its own parent's private attribute.
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 inheritance, super() and the private-versus-protected boundary, but it isn't full Python.
Check your understanding
Route declares inspectionLog as private and timesClimbed as protected. Inside BoulderRoute, which of these is true?
Common mistake
B3.2.1RecurringCandidates state that private members are not inherited. They are: the child object genuinely contains them, and the parent's own public methods can still operate on them. What the child class cannot do is refer to them directly in its own code. Answers that confuse inherited with accessible lose the mark even when the surrounding explanation is sound.
The second recurring error is on reusability questions: describing inheritance as "it saves typing." The creditworthy version names the maintenance consequence, that shared behaviour is defined once so a change to it happens in one place.
Answer frame that scores: name the modifier → say whether the child may access it directly → say what still happens to the member regardless.
What a weak answer looks like: “Private variables are not inherited by the subclass, so BoulderRoute does not have inspectionLog.” The second half is simply untrue, and it is the half that decides the mark.
What it should say: “inspectionLog is private, so although every BoulderRoute object contains it, code written inside BoulderRoute cannot access it directly and must go through a public method of Route. timesClimbed is protected, so BoulderRoute can use it directly.” Modifier named, access stated, and the distinction between holding and reaching made explicit.
Revision questions
Past-paper style. Write an answer, then reveal the marking points and mark yourself.
[2]Identify the parent class and one child class in the Ravensworth hierarchy, and state one attribute the child receives without declaring it.
Marking points
评分要点
- Parent class: Route.
- One child class: BoulderRoute (or RopeRoute).
- One attribute the child receives without declaring it, for example the route code, the grade, or the climb count defined on Route.
- 父类:Route。
- 一个子类:BoulderRoute(或 RopeRoute)。
- 子类无需声明就获得的一个属性,例如在 Route 上定义的线路代码、难度等级或攀爬计数。
[4]The centre adds indoor speed lanes, which have a route code, a grade and a climb count like every other route, plus a recorded fastest time. Construct the class, using inheritance.
Marking points
评分要点
- class SpeedLane(Route): so it inherits the route code, grade and climb count.
- Its constructor calls the parent constructor (super().__init__(...)) to set up the inherited attributes.
- It then adds the new attribute self.fastest_time.
- No need to redeclare the shared attributes; only the fastest-time behaviour is new.
- class SpeedLane(Route):,于是它继承线路代码、难度等级和攀爬计数。
- 它的构造函数调用父类构造函数(super().__init__(...))以设置继承的属性。
- 然后它添加新属性 self.fastest_time。
- 无需重新声明共享的属性;只有最快时间的行为是新的。
[3]RopeRoute needs to read inspectionLog, which is private in Route. Explain why it cannot do so directly, and outline one way to give it proper access.
Marking points
评分要点
- inspectionLog is private in Route, so it is accessible only within Route itself, not from a subclass directly.
- RopeRoute inherits the attribute but cannot name it directly because private access is not extended to subclasses.
- One proper fix: make inspectionLog protected instead of private, or add a public or protected getter method on Route that RopeRoute can call.
- inspectionLog 在 Route 中是私有的,所以只在 Route 自身内部可访问,不能从子类直接访问。
- RopeRoute 继承了这个属性,但不能直接指名它,因为私有访问不延伸到子类。
- 一个恰当的修正:把 inspectionLog 改为受保护而不是私有,或在 Route 上添加一个 RopeRoute 可以调用的公有或受保护的取值方法。
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.1, and every other IB CS 2027 statement.