Design patterns from the 1994 “Gang of Four” book get a bad reputation from overuse in enterprise Java circa 2005, but the underlying problems they solve are still real. Three patterns in particular show up constantly, often without anyone naming them explicitly.
Singleton: Exactly One Instance
class ConfigManager:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._settings = {}
return cls._instance
Useful for things that genuinely should have exactly one instance — a connection pool, a logging configuration. Overused, it becomes global mutable state in disguise, making tests fragile because they secretly share state across runs.
Factory: Delegate Object Creation
class NotificationFactory:
@staticmethod
def create(channel: str):
if channel == "email":
return EmailNotification()
elif channel == "sms":
return SMSNotification()
raise ValueError(f"Unknown channel: {channel}")
The factory hides the decision of exactly which class to instantiate, so calling code doesn’t need a chain of if/elif statements scattered everywhere it needs a notification object.
Observer: Decoupled Event Reactions
class EventEmitter:
def __init__(self):
self._listeners = []
def on(self, callback):
self._listeners.append(callback)
def emit(self, event):
for listener in self._listeners:
listener(event)
emitter = EventEmitter()
emitter.on(lambda e: print(f"Logging: {e}"))
emitter.on(lambda e: print(f"Sending alert for: {e}"))
emitter.emit("disk_space_low")
This is the pattern behind every event listener, webhook system, and pub/sub message queue you’ve ever used — components react to events without knowing anything about each other directly.
The Real Lesson
Patterns are named solutions to recurring problems, not goals in themselves. Reach for one because it solves a real problem you have, not because a codebase “should” have design patterns in it.