Understanding Design Patterns: Singleton, Factory, and Observer

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.