Programming technique · B3.2.5 · Higher level only
Singleton, Factory and Observer
A design pattern is a reusable way of organising classes and objects around a problem that appears repeatedly. The pattern is the design idea, not one exact block of code to memorise.
Start with the recurring problem
Do not begin by asking “which pattern can I force into this program?” Start with the design pressure. Patterns are useful names for solutions that have already proved useful in similar situations.
One shared instance
Many parts of the program need the same object and creating independent copies would be wrong.
SingletonCentralised creation
The program must choose among concrete object types without spreading construction decisions through callers.
FactoryOne-to-many updates
When one object changes or publishes an event, several interested objects should be notified.
ObserverSingleton: one shared object
Suppose the whole application should use one AppSettings object. If every screen creates its own settings object, changing the theme on one copy does not change the others. Singleton provides one shared instance and a controlled way to obtain it.
The classic Java form combines a private constructor, a class-owned instance field and a static access method. Callers cannot use new AppSettings() directly.
Python can enforce one class instance through __new__, although Python programs may also use a module-level shared object when that simpler design is sufficient. The syllabus idea is the single shared instance and controlled access, not one compulsory syntax recipe.
Why it can help
One identity avoids conflicting copies of genuinely shared state and gives the program one known access point.
Why to be cautious
Singleton can behave like global state: dependencies become less visible, tests can interfere with shared state, and many classes may become tightly coupled to the singleton.
Factory: put the creation decision in one place
Suppose a caller needs a badge but the concrete class depends on a choice such as achievement or participation. Without a factory, many callers may repeat the same construction if/else logic.
In Java, the factory can return the shared parent or abstract type. The caller works with Badge while the factory decides which concrete subclass to instantiate.
Python does not need a declared parent type for this small example. The useful idea is still the same: the caller asks the factory for an object and then relies on the common operation rather than constructing each concrete class itself.
Why it can help
Concrete construction is localised. Adding or changing a creation rule can require fewer changes to the rest of the program.
Why to be cautious
A factory adds another layer. If creation is trivial and never varies, the extra class or method may make a small program harder rather than easier to follow.
Observer: publish once, notify many
A school notice board should not contain separate hard-coded calls for every possible display. Instead, interested objects subscribe. When a notice is published, the subject sends the same update to every current observer.
observerStaffDisplay
observer
The important structure is a one-to-many dependency: observers register with the subject, and the subject notifies the registered objects through a common update operation. Adding another observer should not require a new type-specific branch in the publisher.
Why it can help
The publisher is less coupled to individual receivers. New observer types can join the same notification mechanism without rewriting the publishing algorithm.
Why to be cautious
Updates become indirect. With many observers it can be harder to see what an event will trigger, in what order, and whether an observer should be removed.
These patterns reuse ideas from the rest of OOP
| Pattern | Earlier idea it builds on | What changes |
|---|---|---|
| Singleton | Class-owned/static state and encapsulation | Object creation is controlled so the design exposes one shared instance. |
| Factory | Abstraction and polymorphism | Construction of a concrete subtype is moved behind one creation responsibility. |
| Observer | Associations plus common polymorphic behaviour | A publisher keeps references to interested objects and sends each the same update operation. |
Compare before choosing
| Question | Singleton | Factory | Observer |
|---|---|---|---|
| Recurring problem | Too many instances of one shared object | Creation logic spread across callers | Publisher tightly coupled to many receivers |
| Core move | Control access to one instance | Centralise object creation | Register observers and notify them |
| Main benefit | Consistent shared identity/state | Lower coupling to concrete classes | Flexible one-to-many updates |
| Typical cost | Hidden/global-style dependencies | Extra indirection | Less obvious event flow |
Choose the pattern from the problem
Name the recurring design problem first, then justify the pattern.
-
A program needs exactly one shared configuration object that every screen should read from. Which pattern is the best starting point?
Reveal model answer
Singleton. Its intent is to provide one shared instance with a controlled access point. You should still justify why global shared state is appropriate rather than using it automatically.
-
A program must create different exporter objects from a file-format choice, but the caller should not know each concrete exporter class. Which pattern?
Reveal model answer
Factory. It centralises the creation decision and returns an object through a common role or common set of operations.
-
One sensor changes state and several displays should be updated automatically. Which pattern?
Reveal model answer
Observer. The subject/publisher keeps track of observers and notifies them when the relevant event occurs.
-
Does putting every new expression inside a class called SomethingFactory automatically make the design a useful Factory pattern?
Reveal model answer
No. The useful design idea is centralising a recurring object-creation decision so callers depend less on concrete classes. A wrapper with no real creation responsibility only adds indirection.
-
Why can Observer become harder to debug than a direct method call?
Reveal model answer
One event may trigger several indirect updates. The control flow, notification order and current subscription list are less obvious than one direct caller-to-receiver call.
How much implementation do you need?
B3.2.5 uses the command term Explain. You need to recognise Singleton, Factory and Observer, explain the problem each addresses, trace a small example, justify where the pattern is useful and discuss a relevant trade-off. Small completion or extension tasks are useful evidence, but this standard does not require memorising industrial-strength framework implementations.
Challenges Choose one
Choose a challenge that feels appropriate for you. Code heat is only a rough estimate, not a fixed level.
Repair the Shared Audit Log
SelectedRepair the supplied AuditLog design so callers use one shared instance instead of constructing multiple logs. Demonstrate object identity, then add concise comments explaining why Singleton fits this scenario and one disadvantage of using shared global-style state. Do not claim Singleton is automatically the best design whenever a class should be convenient to access.
Extend the Receipt Factory
SelectedComplete the supplied ReceiptFactory and add a PrintReceipt concrete type. The caller must request a receipt through the factory and use only the common Receipt role; it must not contain an if/else chain that constructs concrete receipt subclasses. Test at least two receipt types, then explain how the pattern localises a recurring creation decision and one cost of the extra indirection.
Match Feed Observers
SelectedComplete MatchFeed so listener objects can subscribe and every published match event is sent to all current listeners through the same update operation. Add a CoachDisplay listener and prove that MatchFeed does not need a CoachDisplay-specific branch. Explain why this is Observer, and give one maintainability benefit and one debugging/control-flow cost.
Selected challenge
This choice is shared with the portfolio setup page.
Plan your solution in handwritten pseudocode
Before opening your IDE or writing any program code, handwrite pseudocode for this challenge on paper.
Not marked complete. If you submit now, the GitHub README will record “No”.
Create your challenge folder
Run this command after planning. It creates the correct empty folder inside your portfolio.
Complete the challenge
Use your handwritten pseudocode as the starting plan, then write and test your solution in the folder created above.
Optional two-level scaffold
Try from your handwritten pseudocode first. Scaffold gives some structure; Scaffold + comments gives stronger guidance. Use only the level you need, and update your pseudocode first if the support changes your plan.
Submit for review
Run this when your program is complete. It creates the README, commits the folder and pushes it. The README records whether you marked the handwritten pseudocode as complete; the paper itself is handed to your teacher separately.
B3.2 multiple-class OOP complete
You have now connected inheritance, polymorphism, abstraction, composition/aggregation and three common design patterns. The important skill is not collecting keywords: it is choosing and explaining a class relationship or structure that matches the problem.