B3.2.5
Explain commonly used design patterns in OOP
A design pattern is a reusable solution template for recurring design problems.
What Is a Design Pattern?
A design pattern is a reusable solution template for recurring design problems.
- It is not finished code.
- It is language-agnostic guidance for building robust, maintainable systems.
- It speeds development by reusing proven structures.
Typical Pattern Documentation
Most pattern descriptions include:
- Intent: what problem the pattern solves
- Motivation: why this problem matters in real systems
- Structure: class relationships (often UML)
- Code example: how it can be implemented
Three Pattern Families
| Family | Focus |
|---|---|
| Creational | How objects are created |
| Structural | How classes/objects are composed |
| Behavioral | How objects communicate and cooperate |
For this section, focus on: Singleton, Factory, and Observer.
1) Singleton Pattern
Core idea
Ensure a class has exactly one instance and provide global access to it.
Good use cases
- Shared config manager
- Logging service
- A single connection/controller object
Structure (concept)
- Private/controlled constructor
- One static/class-level instance reference
- One access method (e.g.,
get_instance())
Python example
class SingletonConfig:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.mode = "prod"
return cls._instance
a = SingletonConfig()
b = SingletonConfig()
print(a is b) # True
Pros / Cons
| Pros | Cons |
|---|---|
| Strict control of a shared resource | Harder to test (global state) |
| Guarantees single instance | Can introduce hidden coupling |
| Centralized access point | Lifetime/reset handling can be awkward |
2) Factory Pattern
Core idea
Create objects through a factory method/class instead of direct constructor calls. Client code depends on a common interface, not concrete classes.
Good use cases
- You do not know exact concrete type at compile/design time
- New product types are added over time
- You want to centralize creation logic
Structure (concept)
- Common product interface/base class
- Concrete product subclasses
- Factory that returns interface type but chooses concrete class internally
Python example
class Student:
def level(self) -> str:
raise NotImplementedError
class PrimaryStudent(Student):
def level(self) -> str:
return "Primary"
class MiddleStudent(Student):
def level(self) -> str:
return "Middle"
class StudentFactory:
@staticmethod
def create(stage: str) -> Student:
if stage == "primary":
return PrimaryStudent()
if stage == "middle":
return MiddleStudent()
raise ValueError("Unknown stage")
Pros / Cons
| Pros | Cons |
|---|---|
| Centralized creation logic | More classes/abstractions |
| Easy to add new products | Can become overengineered for small systems |
| Reduces direct coupling to concrete classes | Factory logic may grow complex |
3) Observer Pattern
Core idea
Define a one-to-many subscription mechanism:
- Publisher/Subject stores subscribers
- When state changes, publisher notifies all subscribers
Good use cases
- Notifications
- Event systems
- UI updates from data changes
Structure (concept)
- Subscriber interface (e.g.,
update(...)) - Publisher with
add_subscriber,remove_subscriber,notify - Iteration over subscriber list during notification
Python example
class Subscriber:
def update(self, message: str):
raise NotImplementedError
class EmailSubscriber(Subscriber):
def __init__(self, name: str):
self.name = name
def update(self, message: str):
print(f"{self.name} received: {message}")
class Publisher:
def __init__(self):
self.subscribers: list[Subscriber] = []
def add_subscriber(self, s: Subscriber):
self.subscribers.append(s)
def remove_subscriber(self, s: Subscriber):
self.subscribers.remove(s)
def notify(self, message: str):
for s in self.subscribers:
s.update(message)
Pros / Cons
| Pros | Cons |
|---|---|
| Easy to add new subscribers without changing publisher core | Notification order may be undefined |
| Decouples sender from receivers | Priority/filtered delivery needs extra design |
| Supports scalable event-driven design | Debugging event chains can be harder |