B3.2.5 Explain commonly used design patterns in OOP.
• The key design patterns such as singleton, factory and observer
• The application of design patterns in solving recurring programming challenges
The big idea
In software development, the same kinds of problems appear over and over:
- “I need only one instance of this class.”
- “I want to create objects, but without hard-coding the exact type.”
- “I want different parts of my program to react automatically when something changes.”
Design patterns are reusable solutions to these common problems. They are not code you copy-paste, but templates for solving problems in a consistent, proven way.
Why design patterns matter
- Consistency: Team members recognize patterns and understand the approach quickly.
- Maintainability: Patterns separate concerns and make changes safer.
- Extensibility: Patterns often support adding new features without rewriting existing code.
- Scalability: Patterns encourage designs that handle growth in code size and complexity.
1. Singleton Pattern
Purpose: Ensure that only one instance of a class exists, and provide a global point of access to it.
When to use:
- Configuration managers
- Logging services
- Connection pools
Key characteristics:
- Private constructor (prevents creating new instances from outside).
- A static method or property that returns the single instance.
- Often thread-safe in multi-threaded environments.
Java example:
public class Logger {
private static Logger instance;
private Logger() {} // private constructor
public static Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
Benefits:
- Guarantees a single point of control.
- Saves memory when only one instance is needed.
Drawbacks:
- Can hide dependencies, making testing harder.
- If overused, can act like global variables (tight coupling).
2. Factory Pattern
Purpose: Create objects without specifying the exact class in the client code. The factory method decides which subclass or object to return.
When to use:
- You need to create different types of related objects.
- You want to avoid tightly coupling code to specific classes.
Key characteristics:
- Encapsulates object creation logic.
- Returns objects that share a common interface or base class.
Java example:
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() { System.out.println("Drawing Circle"); }
}
class Square implements Shape {
public void draw() { System.out.println("Drawing Square"); }
}
class ShapeFactory {
public Shape getShape(String shapeType) {
if (shapeType.equalsIgnoreCase("CIRCLE")) return new Circle();
if (shapeType.equalsIgnoreCase("SQUARE")) return new Square();
return null;
}
}
Benefits:
- Isolates object creation, making code easier to modify or extend.
- Reduces duplication of instantiation logic.
Drawbacks:
- Adds an extra layer of abstraction.
- Can become complex if many object types are supported.
3. Observer Pattern
Purpose: Set up a one-to-many relationship between objects so that when one object changes state, all its dependents are notified automatically.
When to use:
- Event systems (e.g., GUI buttons and click listeners)
- Publish–subscribe messaging
- Live data updates in applications
Key characteristics:
- Subject (Publisher): Maintains a list of observers and notifies them when something changes.
- Observer (Subscriber): Defines an update method to be called when the subject changes.
Java example:
import java.util.*;
interface Observer {
void update(String message);
}
class NewsAgency {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer o) { observers.add(o); }
public void removeObserver(Observer o) { observers.remove(o); }
public void notifyObservers(String news) {
for (Observer o : observers) {
o.update(news);
}
}
}
class NewsChannel implements Observer {
private String news;
public void update(String news) {
this.news = news;
System.out.println("Breaking news: " + news);
}
}
Benefits:
- Promotes loose coupling between objects.
- Makes it easy to add new observers without modifying the subject.
Drawbacks:
- Can lead to unexpected update sequences if many observers are involved.
- Requires careful management to avoid memory leaks (observers not removed).
Applying design patterns effectively
- Recognize the problem type – Is it about object creation, controlling access, or coordinating changes?
- Select the right pattern category –
- Creational: control how objects are made (e.g., Singleton, Factory).
- Structural: define how classes and objects are composed (e.g., Adapter, Composite).
- Behavioral: manage communication and responsibilities (e.g., Observer, Strategy).
- Adapt, don’t adopt blindly – Use patterns as guidance, but modify for your project’s specific requirements.
- Document the pattern use – Make it clear to future maintainers why you chose a certain pattern.