First exams 2027 · HL only

B3.2.5 · Fundamentals of OOP for multiple classes (HL)

Design patterns in OOP

Three problems that come up in almost every large program, and the shapes programmers have agreed on for solving them.

HL only Typically 4–6 marks · AO1/AO2 Command term: explain

Explainer

Calderbrook Interchange is building the software behind its departure boards. Three problems come up almost immediately, and none of them is really about buses. There must be exactly one departure log, however many parts of the system want to write to it. Alerts come in several kinds, and the code raising one should not have to know which class to build. And when a service is delayed, several very different displays all need telling at once, without the service knowing what a display even is.

Df

A design pattern is a named, reusable solution to a problem that keeps recurring across different programs. It is not code you copy, it is a shape you apply: a description of which classes exist, what each is responsible for, and how they talk to each other.

Two benefits are worth naming in an answer. Patterns are proven, so you inherit a solution whose weaknesses are already understood rather than inventing one. And they are named, which gives developers shared vocabulary: saying "make it a singleton" communicates an entire design in three words.

The three below are the ones the syllabus asks for. Each is introduced by the recurring problem it exists to answer, because that problem is what the second elaboration bullet is really asking about.

Pattern 1

Singleton

The recurring problem

Something in the system must exist exactly once. If two copies are created by accident, they drift apart and neither is right. At Calderbrook the departure log is like this: two logs would mean two different answers to "what happened this morning?"

Df

Under the singleton pattern, a class is written so that it can never be instantiated twice: the first request creates the object, every later request receives that same one, and the class exposes a single agreed route to it. The class itself takes responsibility for preventing a second instance, rather than trusting every caller to remember.

class DepartureLog:
    _instance = None              # the one instance lives on the CLASS

    def __init__(self):
        self.entries = []

    @staticmethod
    def get():                      # the single agreed way in
        if DepartureLog._instance == None:
            DepartureLog._instance = DepartureLog()
        return DepartureLog._instance

    def record(self, text):
        self.entries.append(text)


# every caller gets the SAME object, so every entry lands in one place
DepartureLog.get().record("Service 12 delayed")
DepartureLog.get().record("Service 4 cancelled")

The give-away is a class variable holding an instance of its own class, plus a method that hands it out. Note that get() is a static method: it has to work before any instance exists, so it cannot need one.

Pattern 2

Factory

The recurring problem

Several related classes exist, and the code that needs one should not have to choose between them. At Calderbrook a delay, a cancellation and a platform change are three different alert classes, but the code reporting an incident only knows a string from an operator's form.

Df

The factory pattern puts object creation behind a single method that decides which class to instantiate and returns it. The caller asks for what it wants by description and receives a ready-made object, without naming any concrete class itself.

class AlertFactory:
    @staticmethod
    def create(kind, detail):        # ONE entry point for three classes
        if kind == "delay":
            return DelayAlert(detail)
        elif kind == "cancelled":
            return CancellationAlert()
        elif kind == "platform":
            return PlatformChangeAlert(detail)
        return None


# the caller never writes DelayAlert(...) or CancellationAlert()
alert = AlertFactory.create("delay", 7)
print(alert.text())

Every alert class offers a text() method, so whatever comes back can be used the same way. That shared interface is what makes the factory safe, and it is abstraction and polymorphism doing the work underneath.

Why this is not just a constructor

A constructor builds one known class, and the caller has to name it. A factory decides which class to build. Adding a fourth alert type means editing the factory only, while every place that reports an incident keeps working untouched.

Pattern 3

Observer

The recurring problem

When one object changes, an unknown number of others need to react, and the changing object should not have to know who they are. At Calderbrook a delayed service must reach the platform screen, the mobile app and the announcement system, and next year something else nobody has built yet.

Df

The observer pattern lets one object (the subject) keep a list of dependent objects (the observers) and notify every one of them automatically when its state changes. Observers subscribe and unsubscribe at run time, and the subject knows nothing about them beyond the fact that they can be notified.

class BusService:                    # the SUBJECT
    def __init__(self, code):
        self.code = code
        self.observers = []          # who wants telling

    def subscribe(self, observer):
        self.observers.append(observer)

    def unsubscribe(self, observer):
        self.observers.remove(observer)

    def announce(self, message):     # tell everyone, whoever they are
        for o in self.observers:
            o.update(self.code, message)


s12 = BusService("Service 12")
s12.subscribe(PlatformScreen("Platform 3"))   # an OBSERVER
s12.subscribe(MobileApp("Calderbrook app"))     # another one
s12.announce("delayed by 7 minutes")

Notice what BusService does not contain: no mention of screens, apps or announcements. It only requires that anything on its list has an update() method. That is what lets a new display type be added years later without touching this class at all.

Explainer

Recognising each one

An exam question usually gives you a situation and asks which pattern fits. Match on the problem, not on the vocabulary.

PatternThe recurring challenge it solvesWhat to look for in code
Singleton Exactly one of something must exist, and everything must share it A class variable holding an instance of its own class, reached through a static method such as get()
Factory Choosing between several related classes, without the caller naming any of them One method that returns different classes depending on its argument
Observer One change must reach many objects, and their number is not known in advance A list of subscribers, a subscribe method, and a loop that calls the same method on each
What all three have in common

Each one removes a dependency. The caller stops depending on when an object was created, or on which class it is, or on who needs telling. That is why patterns make a system easier to extend: the parts know less about each other than they otherwise would.

Try it yourself

All three, in one departure board

The Calderbrook system, live and editable. Worth trying: add a third observer class and subscribe it without touching BusService, and check the singleton really is one object by recording through one handle and reading through another.

main.py
Loading Python engine…
Output
Press Run to see the output here.

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 static methods, list subscription and object factories, but it isn’t full Python.

Check your understanding

A weather app must alert an unknown number of widgets whenever a new reading arrives, and new widget types are added regularly without changing the reading code. Which pattern fits, and why?

Common mistake

Markers reported this pattern for B3.2.5

RecurringCandidates describe the mechanism of a pattern accurately and never say which problem it solves. "A factory has a method that creates objects" is true of every constructor ever written, so on its own it earns little. The mark is in the purpose: the caller does not have to know which class it is getting.

The second error is treating singleton as a synonym for "a class with static methods". A class of static methods holds no state and is never instantiated; a singleton is a real object with real attributes, of which exactly one exists.

Answer frame that scores: name the recurring problem → name the pattern → say what the pattern removes the need for the rest of the program to know.

✕

What a weak answer looks like: “The observer pattern is when objects observe another object and get updated.” This restates the name without naming a problem, and would fit almost any event-driven code.

✓

What it should say: “When a service is delayed, several displays must react, but the number and type of displays change over time. The observer pattern lets each display subscribe to the service, so the service notifies its list without knowing what any of them are, and a new display type can be added without editing BusService.” Problem, pattern, and what it removes.

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 design patterns connect specifically to A4 (machine learning).

A4

How can design patterns in OOP facilitate the architecture of scalable and maintainable machine learning models? (All three patterns on this page appear in real ML systems: a single shared model registry so every request scores against the same loaded weights, a factory that returns whichever model version a request asks for without the caller naming it, and observers that react when training finishes, one logging metrics, another retiring the previous model.)

Revision questions

Past-paper style. Write an answer, then reveal the marking points and mark yourself.

Question 1 · 3 marks

[3]Explain why DepartureLog is implemented as a singleton, referring to what would go wrong without it.

Question 2 · 4 marks

[4]Calderbrook adds a DiversionAlert. Construct the change needed, and state which parts of the existing system must be edited and which must not.

Question 3 · 5 marks

[5]A developer proposes that BusService should call each display directly instead of keeping a list of observers. Evaluate this proposal.

Flashcards

Active recall for this statement’s key terms.