Prototype and Singleton
What This Concept Is
Two creational patterns are grouped together here because their correct usage is narrow and their misuse is famous.
- Prototype: create new instances by cloning an existing one. Useful when construction is expensive, when the client wants to configure an instance once and copy it many times, or when the concrete class is only known at runtime (the "prototype registry" case).
- Singleton: ensure a class has exactly one instance, with a global access point. Famously overused; legitimate when exactly-oneness is a real invariant of the process (not just a convenience).
The problem each absorbs is real, but narrow:
- Prototype: "I need a fresh instance that looks like this one, without walking the construction path again."
- Singleton: "There is one of these by definition -- a thread pool the OS says I only get one of, a hardware handle, a system clock -- and every caller must see the same one."
Why It Matters Here
Singleton is the pattern most likely to harm a codebase. It is often used as a global variable with a better name, which means hidden coupling, untestable state, and shared mutation. Prototype is benign but obscure; you usually only need it when construction cost is high or the class is chosen at runtime.
The engineering lesson in this concept is not "how to implement them" but "how to tell whether you should."
Concrete Example
# Prototype: one pre-configured "shape" copied many times
class EmailTemplate:
def __init__(self, subject, body, footer):
self.subject, self.body, self.footer = subject, body, footer
def clone(self):
return EmailTemplate(self.subject, self.body, self.footer)
welcome = EmailTemplate("Welcome!", "...", "- The Team")
for user in new_users:
note = welcome.clone()
note.body = personalise(note.body, user)
send(user.email, note)
# Singleton: one true clock for the whole process
class Clock:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def now(self): return time.time()
Structural sketch:
Prototype: Singleton:
original --clone--> copy Caller --getInstance()--> the one instance
original --clone--> copy Caller --getInstance()--> the one instance
original --clone--> copy (Caller never constructs directly)
Common Confusion / Misconception
- A Singleton is not the same as a "utility class with static methods." Singleton implies state; static methods are stateless.
- "We only want one for now" is not a Singleton case. That is a scoping concern the composition root solves cleaner.
- Prototype is not "deep copy on demand." It is a configured original whose shape is the thing being cloned.
- Singleton frequently hides a need for dependency injection: if what you wanted was "there is one of these in this run," inject it.
How To Use It
For Prototype:
- Confirm construction is actually expensive or the concrete class is runtime-chosen.
- Make
clone()honest: deep vs shallow explicit, no shared mutable state leaking. - Consider a registry of prototypes keyed by type when the plugin-style story fits.
For Singleton:
- Prove exactly-oneness is an invariant, not a convenience.
- Prefer DI-supplied single instances over
getInstance(). - If you still want a Singleton, hide the fact behind an interface so tests can replace it.
- Never use Singletons for configuration, logging filters, or "whatever the current user is" -- those are scopes, not singletons.
Check Yourself
- Why is "we only ever need one" usually not a Singleton justification?
- What testing pain does a global Singleton reliably introduce?
- When is Prototype the right answer instead of a constructor?
Mini Drill or Application
- Find a Singleton in an existing codebase. Sketch the change needed to replace it with constructor injection. Estimate how many call sites change.
- Take a class with an expensive constructor and introduce a prototype +
clone(). Measure the actual cost difference with a microbenchmark before keeping it. - List three things people call "singletons" that are actually process-scoped or request-scoped values.