B3.1.1 · Fundamentals of OOP for a single class
The fundamentals of object-oriented programming
Five concepts, one recurring pattern: model the real world as things that hold their own data and know how to act on it.
Explainer
A smart-home app has to track a front-door lock, a thermostat, and a hallway light. All three are switched on or off. All three log a battery level. But locking a door and dimming a light are completely different actions. Writing one giant function full of if device_type == "lock" checks to handle every device would work, but it would grow unmanageable the moment a fourth device type showed up. Object-oriented programming (OOP) solves this by building each device as its own self-contained unit that knows how to behave.
Object-oriented programming is a programming paradigm that models a system as a collection of interacting objects. Each object holds its own state, and the only code allowed to change that state lives inside the object itself, rather than in one long sequence of procedures all reaching into shared data.
The whole paradigm rests on five ideas that keep showing up together: classes and objects (the blueprint and the real things built from it), inheritance (sharing a blueprint between related things), encapsulation (each thing guarding its own data), and polymorphism (related things responding to the same instruction in their own way).
Worked example
Five concepts, one smart-home app
Every concept below is inspected through the same running scenario: a Device blueprint, and three real devices built from variations on it. Click through them in any order.
Class: the blueprint
A class defines what every device of that kind will have and be able to do, without describing any one real device yet. Nothing has been switched on or off until an actual object exists.
# the blueprint: no real device exists yet, just the shape every Light will have class Light: def __init__(self, device_id): self.device_id = device_id self.is_on = False self.brightness = 0 def toggle(self): self.is_on = not self.is_on
In this app: writing class Light: once defines every light the app will ever create, from one blueprint.
Object: one real thing built from the blueprint
An object (an instance of the class) is one concrete device with its own state. Creating two objects from the same class gives two independent things, changing one never touches the other.
# two separate Light OBJECTS, each with its own independent state hallway_light = Light("L01") kitchen_light = Light("L02") hallway_light.toggle() # hallway_light.is_on becomes True # kitchen_light.is_on is still False, it is a completely separate object
In this app: hallway_light and kitchen_light share the same class, but toggling one never affects the other.
Inheritance: sharing a blueprint between related things
A lock, a thermostat, and a light are all still “devices”: each has a device_id, a battery level, and can be toggled. Rather than writing that shared behaviour three times, a general Device class defines it once, and Light, Thermostat, and Lock each inherit from it, adding only what makes them different.
# Device holds what every device has in common class Device: def __init__(self, device_id): self.device_id = device_id self.is_on = False self.battery_level = 100 def toggle(self): self.is_on = not self.is_on # Light INHERITS device_id, is_on, battery_level and toggle() for free class Light(Device): def __init__(self, device_id): super().__init__(device_id) self.brightness = 0
In this app: Light(Device) means every light already has an ID, a battery level, and can toggle, none of that had to be rewritten.
Encapsulation: each device guards its own data
The battery level is stored inside the device itself, not as a value floating around the rest of the program. Reading or draining it happens through the device's own methods, so the device can enforce its own rule: it never reports a battery level below zero.
# battery_level is only ever touched through the device's own methods class Device: def __init__(self, device_id): self.device_id = device_id self.__battery_level = 100 # hidden def drain_battery(self, amount): self.__battery_level = max(0, self.__battery_level - amount) def get_battery_level(self): return self.__battery_level
In this app: no code outside Device can set __battery_level to a negative number, since nothing outside the class can touch it directly at all.
Polymorphism: the same instruction, a different response
Every device understands get_status(), but each kind of device answers it in its own way: a light reports brightness, a thermostat reports temperature, a lock reports whether it's locked. The caller sends the same message to any device without needing to know which kind it is.
# same method NAME, three different device-specific IMPLEMENTATIONS class Light(Device): def get_status(self): return f"brightness {self.brightness}%" class Thermostat(Device): def get_status(self): return f"set to {self.target_temp} deg" class Lock(Device): def get_status(self): return "locked" if self.is_locked else "unlocked"
In this app: calling device.get_status() works identically for any device, but which version actually runs depends on which kind of device it is.
Worked example
Why this matters: all five together
The real payoff shows up once several different device objects need to be handled the same way. This is the exact scenario from the explainer at the top of the page, solved properly.
# one loop, three different device TYPES, zero if/elif chains devices = [Light("L01"), Thermostat("T01"), Lock("K01")] for device in devices: print(device.device_id, ":", device.get_status()) # adding a fourth device type later needs no change to this loop at all
Each object (instantiated from a class that inherits from Device) hides its own state (encapsulation) and responds to get_status() in its own way (polymorphism). The loop never has to ask “what kind of device is this,” it just trusts every object to know how to answer.
Class defines the shape. Object is one real thing with that shape. Inheritance lets related shapes share what's common. Encapsulation keeps each object's data safe inside it. Polymorphism lets the same instruction get a different, correct answer from each kind of object.
Try it yourself
Edit the smart-home scenario and run it
This is the same Device/Light/Thermostat/Lock code from above, live and editable. Try adding a fourth device class (a Fan, say) without touching the loop, that's the whole payoff of polymorphism made concrete.
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 can still run this page's own scenario, classes, inheritance, self/super(), if/elif/else, loops over a list, but it isn't full Python.
Check your understanding
In the smart-home example, which OOP concept lets a single loop call device.get_status() on a Light, a Thermostat, and a Lock, and get the correct kind of answer from each, without the loop ever checking the device's type?
Common mistake
B3.1.1RecurringCandidates frequently swap the words class and object, describing a class as “a single device” or an object as “the blueprint,” which loses marks even when the surrounding explanation shows real understanding of the mechanism.
Answer frame that scores: name the class as the definition/template → name the object as one specific instance created from it → give a concrete example of each from the scenario in the question.
What a weak answer looks like: “Light is an object that stores the brightness of the hallway light.” This calls the class an object, and describes ONE specific light as if it were the general definition.
What it should say: “Light is the class: it defines that every light has a brightness and can be toggled. hallway_light is an object, one specific instance of Light, with its own actual brightness value.” Class and object are named correctly and tied to a concrete example.
Explainer
Advantages and disadvantages of choosing OOP
“Evaluate” questions on this statement expect a real trade-off, not a one-sided list, and B3's own guiding question asks exactly this: is OOP the right paradigm here, or would a simpler approach do?
Real example: one handle_device(device_type, state) function with an if/elif chain for every device type.
Advantage
- Fast to write for a small, fixed set of cases (no classes, no inheritance hierarchy to design first)
Disadvantage
- Every new device type means editing the same shared function (a fourth device type forces a change to code that already works for the first three, risking a new bug in previously-tested logic)
Real example: the Device/Light/Thermostat/Lock hierarchy above, called through one polymorphic loop.
Advantage
- A new device type is a new class, added without touching existing ones (the loop that calls get_status() never changes, so working code for Light and Thermostat can't be broken by adding Lock)
Disadvantage
- More design effort upfront (deciding what belongs in the shared Device class versus each subclass takes real planning before any device-specific code is written)
The trade-off is scale versus overhead: OOP earns its cost back once a system has multiple related, evolving entity types, which is exactly the kind of complex, growing problem B3's guiding question is asking about. A one-off script with a single fixed behaviour rarely needs it.
Linking question
How this connects to the rest of the syllabus
The IB syllabus is deliberately woven together, not a stack of isolated topics. Here's how OOP connects specifically to B2 (data structures and algorithms).
Is OOP necessary for all programming, or only in modelling complex situations? (The smart-home example above shows the actual trade-off directly: a single fixed algorithm rarely benefits from classes at all, but a system of several related, evolving entity types is exactly where the extra design effort pays off.)
Revision questions
Past-paper style. Write an answer, then reveal the marking points and mark yourself.
[2]Distinguish between a class and an object, using the Light class as an example.
Marking points
评分要点
- A class is a blueprint: Light defines the attributes (device_id, is_on, brightness) and methods (toggle) that every light will have.
- An object is a specific instance built from that blueprint, for example one particular Light with its own device_id and its own on/off state.
- 类是一个蓝图:Light 定义了每盏灯将拥有的属性(device_id、is_on、brightness)和方法(toggle)。
- 对象是从该蓝图构建的一个具体实例,例如某一盏特定的 Light,有它自己的 device_id 和自己的开关状态。
[3]Outline how inheritance reduces duplicated code across the Light, Thermostat, and Lock classes.
Marking points
评分要点
- Light, Thermostat and Lock share behaviour such as an identifier, a connected state and a way to report status.
- Putting that common code in a shared parent class (for example Device) means it is written once, not three times.
- Each of Light, Thermostat and Lock then inherits it and adds only what is specific to that device.
- A change or fix to the shared behaviour is made once in the parent and applies to all three.
- Light、Thermostat 和 Lock 共享一些行为,如一个标识符、一个连接状态和一种报告状态的方式。
- 把这些公共代码放在一个共享的父类中(例如 Device),意味着它只写一次,而不是三次。
- 然后 Light、Thermostat 和 Lock 各自继承它,只添加该设备特有的部分。
- 对共享行为的改动或修复在父类中做一次,即应用到三者。
[4]Evaluate whether an object-oriented approach is appropriate for a program that only ever needs to reverse a single list of numbers.
Marking points
评分要点
- Reversing a single list has no state to model, no variety of related things, and no behaviour to attach to data.
- An OOP approach would add a class, a constructor and method syntax with no reuse or clarity benefit.
- A single function (or one line) is simpler, shorter and easier to test.
- Conclusion: OOP is not appropriate here; it is suited to programs with multiple interacting entities that have state and behaviour.
- 反转一个列表没有状态需要建模,没有相关事物的多样性,也没有需要附着到数据上的行为。
- 面向对象的做法会增加一个类、一个构造函数和方法语法,却没有复用或清晰度上的好处。
- 单个函数(或一行代码)更简单、更短、更易测试。
- 结论:这里不适合面向对象;它适合有多个相互作用、具备状态和行为的实体的程序。
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.1.1, and every other IB CS 2027 statement.