Pro Content

Upgrade to access Singleton, Observer, and all Cambridge 9618 design pattern lessons.

Upgrade to Pro →
← Back to Dashboard
📗 Paper 4 · 4.2 Further OOP
4.2.2 Design Patterns — Singleton & Observer
Cambridge 9618 · International A Level Computer Science · ~17 min read
Notes
Video
Slides
Quiz
Worksheet

What are Design Patterns?

A design pattern is a reusable, proven solution to a commonly occurring problem in software design. Patterns are not finished code — they are templates that guide how to structure classes and their relationships to solve a specific type of problem.

Design patterns fall into three categories:

Creational
How objects are created.

Examples: Singleton, Factory, Builder
Structural
How classes are composed/structured.

Examples: Adapter, Decorator, Facade
Behavioural
How objects communicate.

Examples: Observer, Strategy, Command

Cambridge 9618 focuses on two key patterns: Singleton (creational) and Observer (behavioural).

The Singleton Pattern

The Singleton pattern ensures that a class has only one instance throughout the program, and provides a global access point to that instance. It is used when exactly one object is needed to coordinate actions across the system.

Real-world use cases

  • Database connection pool — one shared connection, not one per class
  • Logger — all parts of the system write to a single log
  • Configuration manager — one settings object loaded once
  • Print spooler — all print jobs go through one spooler

How it works

Singleton — three components
Private constructor — prevents external code from calling NEW Database() directly. Only the class itself can create an instance.
Private static instance — a class-level variable (not per-object) that holds the single instance. Starts as NULL/undefined.
Public static getInstance() — the only way to get the object. Checks if instance is NULL; if so creates it (lazy instantiation); always returns the same instance.

Singleton pseudocode

CLASS DatabaseConnection
  PRIVATE STATIC instance : DatabaseConnectionNULL
  PRIVATE connectionString : STRING

  // Private constructor — cannot be called from outside
  PRIVATE PROCEDURE NEW()
    connectionString"server=localhost;db=mydb"
  ENDPROCEDURE

  // Public static factory method — the only way to get an instance
  PUBLIC STATIC FUNCTION getInstance() RETURNS DatabaseConnection
    IF instance = NULL THEN
      instanceNEW DatabaseConnection()  // lazy creation
    ENDIF
    RETURN instance
  ENDFUNCTION

  PUBLIC FUNCTION query(sql : STRING) RETURNS STRING
    // execute the SQL query and return results
  ENDFUNCTION
ENDCLASS

// Usage — always the same object returned
db1DatabaseConnection.getInstance()
db2DatabaseConnection.getInstance()
// db1 and db2 are the same object — only one was ever created

Singleton UML diagram

DatabaseConnection
instance : DatabaseConnection
connectionString : String
NEW()
+ getInstance() : DatabaseConnection
+ query(sql:String) : String

Underline indicates STATIC (class-level) attribute or method

The Observer Pattern

The Observer pattern defines a one-to-many dependency between a Subject (also called Publisher or Observable) and multiple Observers (also called Subscribers or Listeners). When the Subject changes state, all registered Observers are automatically notified and updated.

This is also called the publish-subscribe model.

Real-world use cases

  • GUI event handling — button clicked → multiple listeners notified
  • Stock price system — price changes → all subscribers (displays, alerts) notified
  • Weather station — temperature changes → all displays (phone, web, display board) updated
  • News feed — new article published → all subscribers receive it

Observer diagram

Subject (StockMarket)
− observers : List
− currentPrice : Real
+ register(obs)
+ remove(obs)
+ notify()
+ setPrice(p)
→ notify →
1 to *
📱 PhoneAlert — update() — shows push notification when price crosses threshold
🖥 TradingScreen — update() — refreshes price display in real time
📊 AnalyticsLogger — update() — records price change in history log

Observer pseudocode

// Observer interface — all concrete observers must implement update()
CLASS Observer  // Abstract
  PUBLIC ABSTRACT PROCEDURE update(newValue : REAL)
ENDCLASS

// Subject class — maintains a list of observers
CLASS StockMarket
  PRIVATE observers : ARRAY OF Observer
  PRIVATE price : REAL
  PRIVATE count : INTEGER0

  PUBLIC PROCEDURE register(obs : Observer)
    countcount + 1
    observers[count] ← obs
  ENDPROCEDURE

  PUBLIC PROCEDURE notify()
    FOR i1 TO count
      observers[i].update(price)
    NEXT i
  ENDPROCEDURE

  PUBLIC PROCEDURE setPrice(p : REAL)
    pricep
    notify()  // automatically notify all observers
  ENDPROCEDURE
ENDCLASS

// Concrete observer
CLASS PhoneAlert INHERITS Observer
  PUBLIC PROCEDURE update(newValue : REAL)
    OUTPUT "Alert: price changed to " + newValue
  ENDPROCEDURE
ENDCLASS

Pattern comparison

FeatureSingletonObserver
CategoryCreationalBehavioural
PurposeEnsure only one instance existsNotify many objects of a state change
Key mechanismPrivate constructor + static getInstance()register/notify/update methods
CouplingTight — all code goes to one objectLoose — subject doesn't know observer details
Use whenShared resource with one instance neededMultiple objects need to react to one change
Cambridge 9618 exam tip: Be able to: (1) explain why the Singleton constructor is private — to prevent external code from creating additional instances; (2) explain lazy instantiation — the instance is only created on the FIRST call to getInstance(), not when the class is loaded; (3) describe the Observer roles — Subject holds the list and calls notify(); Observer interface defines update(); concrete observers implement update() with their specific response; (4) describe a real-world scenario for each pattern. Questions often give a scenario and ask you to identify which pattern applies and justify your answer.
⚠️ Common Mistakes
  • Making the Singleton instance attribute non-static — the instance variable MUST be STATIC (class-level, not per-object) so it persists across all calls to getInstance() and is shared by all references to the class.
  • Making the Singleton constructor public — a public constructor defeats the pattern, as external code could still call NEW DatabaseConnection() directly. The constructor must be PRIVATE.
  • Confusing the Subject and Observer roles — the Subject maintains the list of observers and calls notify(). Observers implement the update() method. The subject should not know the concrete type of each observer — it calls update() through the Observer interface.
  • Saying Observer creates "copies" of data — the Subject doesn't copy data to each observer. It calls each observer's update() method and passes the new value. Each observer then decides independently what to do with the new value.
  • Forgetting that Observer uses polymorphism — when Subject calls observers[i].update(price), this is polymorphism: PhoneAlert, TradingScreen, and AnalyticsLogger each have their own update() implementation. The Subject doesn't need to know which concrete type each observer is.
✅ Notes completed!
Video coming soon
Click slide or press arrow keys to navigate

Worksheet — 4.2.2 Design Patterns

8 questions · Cambridge 9618 standard

Q1Explain what a design pattern is. State the three categories of design pattern and give one example of a pattern from each category.[4]
✅ Mark scheme
A design pattern is a reusable, general solution to a commonly occurring problem in software design — a template for solving a known type of problem, not finished code [1]; Creational: how objects are created — example: Singleton, Factory [1]; Structural: how classes and objects are composed/combined — example: Adapter, Decorator [1]; Behavioural: how objects communicate and distribute responsibility — example: Observer, Strategy [1].
Q2Explain why the Singleton pattern uses (a) a private constructor, and (b) a static getInstance() method. What is meant by lazy instantiation?[4]
✅ Mark scheme
(a) Private constructor: prevents external code from creating instances by calling NEW directly — only the class itself can call NEW [1]; this guarantees no additional instances are created outside the class, enforcing the single-instance rule [1]. (b) Static getInstance() method: a class-level (not per-object) method that provides the only way to access the single instance; it checks if the instance has been created and creates it if not, otherwise returns the existing one [1]. Lazy instantiation: the single instance is not created when the class is first loaded — it is only created on the FIRST call to getInstance(); this saves memory if the singleton is never needed [1].
Q3Describe the roles of Subject and Observer in the Observer pattern. Explain what happens when the Subject's state changes.[4]
✅ Mark scheme
Subject: maintains a list of registered Observer objects; provides methods to register() and remove() observers; when its state changes, calls notify() which calls update() on each observer [1]; Observer: defines an interface with an update() method; each concrete observer implements update() with its specific response to the state change [1]; when the Subject's state changes (e.g. setPrice() is called): it updates its internal state; it calls notify() which loops through the observer list and calls update(newValue) on each; each observer then independently decides what to do — e.g. display the new price, send an alert, log it [1]; the Subject does not need to know the concrete types of its observers — it communicates through the Observer interface, giving loose coupling [1].
Q4Give two distinct real-world scenarios where the Singleton pattern would be appropriate. Explain why Singleton is suitable in each case.[4]
✅ Mark scheme
Any two of: Database connection pool: only one set of connections should exist to prevent resource waste; multiple modules should share the same pool rather than each creating their own [1][1]; Logger: all parts of the system should write to the same log file; multiple loggers would produce fragmented or duplicate logs [1][1]; Configuration manager: system settings should be loaded once from file and shared; multiple instances could have different cached settings causing inconsistency [1][1]; Print spooler: all print jobs must be queued through one spooler to manage printer access; multiple spoolers would conflict [1][1]. Award 1 mark for each scenario + 1 mark for valid explanation of why Singleton is suitable.
Q5Explain how polymorphism is used in the Observer pattern. Why is this beneficial for adding new observer types?[3]
✅ Mark scheme
All concrete observers (PhoneAlert, TradingScreen, AnalyticsLogger) implement the same Observer interface/superclass and override the update() method [1]; the Subject holds a list typed as Observer (the superclass/interface), so it can store any concrete observer regardless of its actual type; when it calls observers[i].update(price), polymorphism means the correct concrete implementation runs for each observer — this is runtime/dynamic polymorphism [1]; benefit: to add a new observer type (e.g. EmailAlert), you simply create a new class that implements Observer and override update(); no changes needed to the Subject or any existing observer class — the system is open for extension without modification [1].
Q6A social media app has one notification manager that routes all alerts. Multiple features (likes, comments, messages) register to receive alerts. Identify which design pattern applies to each requirement and justify your answer.[4]
✅ Mark scheme
"One notification manager" → Singleton pattern: there should be exactly one NotificationManager instance; making it Singleton ensures all parts of the app use the same manager — not multiple competing managers; implement with private constructor and static getInstance() [1][1]; "Multiple features register to receive alerts" → Observer pattern: the NotificationManager is the Subject; the features (LikeModule, CommentModule, MessageModule) are Observers that register with the manager; when an event occurs, the manager calls notify() → each module's update() runs its specific alert logic [1][1].
Q7Describe the Observer design pattern. Identify the roles of: Subject, Observer interface, and ConcreteObserver. Then write Cambridge 9618 pseudocode for a WeatherStation (Subject) that maintains a list of Observer objects and calls their Update() method when temperature changes.[5]
✅ Mark scheme
Observer pattern: Subject maintains list of Observers; notifies all observers when its state changes — decouples data source from consumers [1]; Subject role: holds observer list, provides Register/Unregister and Notify methods; Observer interface: defines Update() contract; ConcreteObserver: implements Update() with specific reaction [1]; CLASS WeatherStation; PRIVATE observers : ARRAY OF Observer; PRIVATE temperature : REAL [1]; PUBLIC PROCEDURE Register(o:Observer) — add to observers array [1]; PUBLIC PROCEDURE SetTemperature(t:REAL) temperature←t; FOR EACH obs IN observers obs.Update(temperature) NEXT ENDPROCEDURE [1].
Q8A weather application needs exactly one WeatherData object shared across the whole system. (a) Name the design pattern that solves this. (b) Explain the mechanism it uses to enforce a single instance. (c) Write pseudocode for the GetInstance() method that returns the single instance.[4]
✅ Mark scheme
(a) Singleton pattern [1]; (b) The constructor is PRIVATE so external code cannot call NEW WeatherData(); a class-level (static) variable holds the single instance; a PUBLIC class method GetInstance() checks if the instance is NULL — if so, creates it; otherwise returns the existing instance [1]; (c) PUBLIC FUNCTION GetInstance() RETURNS WeatherData; IF instance = NULL THEN instance ← NEW WeatherData() ENDIF; RETURN instance ENDFUNCTION [2].
Topic Quiz
Question 1 of 10
You scored
out of 10
Card 1 of 8
Click to reveal definition
🎉
All cards reviewed!
TermDefinition
🎯

Mini Test — 4.2.2 Design Patterns

10 questions · 10 marks · 10 minutes

← 4.2.1 UML & Class Design
69 of 82 · Cambridge 9618
4.2.3 Exception Handling →