OOP Concepts · Lesson 7 of 8 · Updated ·
Design Patterns for Beginners — Singleton, Factory, Observer, Strategy
A design pattern is a named, proven solution to a problem that keeps coming up in software design. Knowing a few patterns helps you write better code and talk clearly with senior developers.
Patterns are grouped as creational (how objects are made), structural (how they're combined) and behavioural (how they communicate).
1. Singleton (creational)
Problem: some things must exist only once — app configuration, a logger, a connection pool.
class AppConfig {
private static AppConfig instance;
private final String env;
private AppConfig() { env = "production"; } // private: no one else can call new
static synchronized AppConfig get() {
if (instance == null) instance = new AppConfig();
return instance;
}
String env() { return env; }
}
public class Main {
public static void main(String[] args) {
AppConfig a = AppConfig.get(), b = AppConfig.get();
System.out.println((a == b) + " " + a.env());
}
}
true production
Use sparingly: singletons are global state and make testing harder.
2. Factory (creational)
Problem: the code shouldn't need to know which class to create — a factory decides.
class Upi:
def pay(self, amt): return f"UPI ₹{amt}"
class Card:
def pay(self, amt): return f"Card ₹{amt}"
class NetBanking:
def pay(self, amt): return f"NetBanking ₹{amt}"
def payment_factory(mode):
methods = {"upi": Upi, "card": Card, "netbanking": NetBanking}
if mode not in methods:
raise ValueError(f"unknown payment mode: {mode}")
return methods[mode]()
for mode in ["upi", "card"]:
print(payment_factory(mode).pay(999))
UPI ₹999
Card ₹999
Seen in: Calendar.getInstance(), database drivers, UI toolkits.
3. Observer (behavioural)
Problem: when something changes, many others must be notified — without the source knowing them all in advance.
class Channel:
def __init__(self, name):
self.name, self.subscribers = name, []
def subscribe(self, fn):
self.subscribers.append(fn)
def publish(self, video):
for notify in self.subscribers:
notify(f"{self.name} uploaded: {video}")
ch = Channel("Skills101")
ch.subscribe(lambda msg: print("Asha's phone →", msg))
ch.subscribe(lambda msg: print("Ravi's email →", msg))
ch.publish("SQL Joins in 10 minutes")
Asha's phone → Skills101 uploaded: SQL Joins in 10 minutes
Ravi's email → Skills101 uploaded: SQL Joins in 10 minutes
Seen in: YouTube notifications, button click listeners, event systems.
4. Strategy (behavioural)
Problem: you need interchangeable algorithms chosen at runtime (delivery fee rules, sorting methods, payment routes).
def standard(km): return 30 + 5 * km
def express(km): return 60 + 8 * km
def free_above_499(km): return 0
class Checkout:
def __init__(self, fee_strategy):
self.fee = fee_strategy
def total(self, cart, km):
return cart + self.fee(km)
print(Checkout(standard).total(350, 4), Checkout(express).total(350, 4), Checkout(free_above_499).total(650, 4))
400 442 650
Strategy replaces long if/else chains with plug-in objects or functions.
Other names worth recognising
| Pattern | One line |
|---|---|
| Builder | build a complex object step by step (StringBuilder, Request.builder()) |
| Adapter | make one interface fit another (a charger adapter) |
| Decorator | add behaviour by wrapping (Java I/O streams, Python @decorators) |
| MVC | separate Model, View and Controller in web apps |
Common mistakes
- Using patterns everywhere to look clever. Use one when its problem actually appears.
- Singleton without thread safety in multi-threaded code.
Quick recap
- Singleton: one instance. Factory: decide which class to create.
- Observer: notify subscribers on change. Strategy: swap algorithms at runtime.
- Patterns are shared vocabulary for common design problems.