Creational Design Patterns (GoF)
The five GoF creational patterns deal with object creation mechanisms. They abstract the instantiation process, making systems independent of how their objects are created, composed, and represented.
Reference: refactoring.guru / Design Patterns — illustrated explanations of every GoF pattern; cross-reference each pattern below against the canonical descriptions there.
The five creational patterns:
| Pattern | Intent | refactoring.guru |
|---|---|---|
| Singleton | One instance, global access | https://refactoring.guru/design-patterns/singleton |
| Factory Method | Subclasses choose what to create | https://refactoring.guru/design-patterns/factory-method |
| Abstract Factory | Families of related products | https://refactoring.guru/design-patterns/abstract-factory |
| Builder | Step-by-step complex construction | https://refactoring.guru/design-patterns/builder |
| Prototype | Clone existing objects | https://refactoring.guru/design-patterns/prototype |
For structural patterns see 06_structural_patterns.md; for behavioral 07_behavioral_patterns.md.
Singleton
Intent: Ensure a class has only one instance and provide a global point of access to it. Reference: https://refactoring.guru/design-patterns/singleton
Problem: You need exactly one instance of a class (a config registry, a logger, a connection pool) shared across the application; allowing multiple instances would cause bugs or wasted resources.
Solution: Make the class itself responsible for tracking its sole instance; expose a method that returns it (creating on first access).
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not hasattr(self, "_initialized"):
self.data = []
self._initialized = True
# Thread-safe variant
from threading import Lock
class ThreadSafeSingleton:
_instance = None
_lock = Lock()
def __new__(cls):
if cls._instance is None: # double-checked locking
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
When to use:
- App-wide config / logger / metrics registry.
- Hardware-bound resources (one printer driver).
- Connection pools (though usually a pool inside a regular class is cleaner).
When NOT to use:
- Global state harms testability. Singletons are hard to mock.
- Hidden dependencies — callers don’t see the Singleton import; coupling is invisible.
- Multi-threaded apps with non-locking implementations cause race conditions.
Pythonic alternatives:
- A module-level variable IS effectively a Singleton (modules are imported once).
functools.lru_cacheon a factory function.- Dependency injection instead of global access.
Common pitfalls:
- Forgetting that
__init__runs on everySingleton()call (only__new__is gated). Guard initialization with a_initializedflag. - Pickling / forking can produce multiple instances unexpectedly.
- Testing tightly-coupled Singleton consumers is painful.
Factory Method
Intent: Define an interface for creating an object, but let subclasses decide which class to instantiate. Defers instantiation to subclasses. Reference: https://refactoring.guru/design-patterns/factory-method
Problem: A class needs to create objects, but the specific class depends on subclass / runtime conditions. Hard-coding Dog() or Cat() inside parent code blocks extension.
Solution: Replace direct constructor calls with calls to a factory method that subclasses can override.
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self) -> str: ...
class Dog(Animal):
def speak(self): return "Woof!"
class Cat(Animal):
def speak(self): return "Meow!"
class AnimalShelter(ABC):
@abstractmethod
def create_animal(self) -> Animal: ... # the factory method
def adopt(self): # business logic uses the factory
animal = self.create_animal()
return f"Adopted! {animal.speak()}"
class DogShelter(AnimalShelter):
def create_animal(self): return Dog()
class CatShelter(AnimalShelter):
def create_animal(self): return Cat()
print(DogShelter().adopt()) # "Adopted! Woof!"
When to use:
- Class delegates responsibility for instantiation to subclasses.
- Different products needed at different points without
if/elif/elseon type. - Framework code creates objects whose types client code (subclasses) decides.
Difference from “factory function”:
- The GoF Factory Method is a SUBCLASS-based pattern (each subclass overrides).
- A “factory function” (
create_user(...)) is a simpler pattern — often what Python developers actually need, called Simple Factory informally.
Common pitfalls:
- Over-engineering: a simple factory function suffices for most cases.
- Forced subclassing when composition would be cleaner.
- “Factory method” everywhere just because it sounds OO.
Abstract Factory
Intent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes. Reference: https://refactoring.guru/design-patterns/abstract-factory
Problem: You need to create families of objects (e.g., Windows-style Button + Checkbox + Menu vs Mac-style Button + Checkbox + Menu) where each family is internally consistent.
Solution: Define an interface for the factory that creates each product type; concrete factories implement the family.
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def paint(self) -> str: ...
class Checkbox(ABC):
@abstractmethod
def paint(self) -> str: ...
class WinButton(Button):
def paint(self): return "Windows button"
class WinCheckbox(Checkbox):
def paint(self): return "Windows checkbox"
class MacButton(Button):
def paint(self): return "Mac button"
class MacCheckbox(Checkbox):
def paint(self): return "Mac checkbox"
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button: ...
@abstractmethod
def create_checkbox(self) -> Checkbox: ...
class WinFactory(GUIFactory):
def create_button(self): return WinButton()
def create_checkbox(self): return WinCheckbox()
class MacFactory(GUIFactory):
def create_button(self): return MacButton()
def create_checkbox(self): return MacCheckbox()
def render_dialog(factory: GUIFactory):
button = factory.create_button()
checkbox = factory.create_checkbox()
return [button.paint(), checkbox.paint()]
factory = WinFactory() if platform == "windows" else MacFactory()
print(render_dialog(factory))
Abstract Factory vs Factory Method:
- Factory Method: one product type, subclass-per-product.
- Abstract Factory: family of related products, factory-per-family.
When to use:
- The system must be independent of how products are created/composed.
- Multiple families of products with internal consistency requirements (Windows widgets only work with Windows look-and-feel).
- Adding new families is more important than adding new products.
Common pitfalls:
- Adding a new product type requires changing the factory interface AND every concrete factory. Painful at scale.
- Over-engineering for “what if we need another DB / theme / region” that never materializes.
Builder
Intent: Separate the construction of a complex object from its representation, allowing the same construction process to create different representations. Reference: https://refactoring.guru/design-patterns/builder
Problem: A class has many optional parameters; constructors with 8+ args are unreadable. Some combinations are invalid; you need step-by-step construction with validation.
Solution: Move construction logic into a separate Builder class with fluent methods; produce the final object at the end.
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class HTTPRequest:
url: str
method: str = "GET"
headers: dict = field(default_factory=dict)
body: Optional[bytes] = None
timeout: float = 30.0
retries: int = 0
class HTTPRequestBuilder:
def __init__(self, url: str):
self._req = HTTPRequest(url=url)
def method(self, m: str):
self._req.method = m
return self # return self for chaining
def header(self, name: str, value: str):
self._req.headers[name] = value
return self
def body(self, data: bytes):
self._req.body = data
return self
def timeout(self, seconds: float):
self._req.timeout = seconds
return self
def retries(self, n: int):
self._req.retries = n
return self
def build(self) -> HTTPRequest:
# validate before returning
if self._req.method == "GET" and self._req.body:
raise ValueError("GET requests cannot have a body")
return self._req
req = (HTTPRequestBuilder("https://api.example.com/users")
.method("POST")
.header("Content-Type", "application/json")
.body(b'{"name": "alice"}')
.timeout(10.0)
.retries(3)
.build())
When to use:
- Constructor takes many optional arguments.
- Construction has multiple steps; some require validation between steps.
- Same construction code should produce different representations (Director pattern).
Pythonic alternatives:
dataclasseswith defaults (often sufficient).- Keyword-only arguments with
*separator. - Named tuples for immutable cases.
- The Builder pattern shines when validation between steps or fluent chaining benefits readability significantly.
Common pitfalls:
- Building a Builder for a 2-argument class — pure ceremony.
- Forgetting that intermediate state may be invalid; the Builder produces the final, validated object.
Prototype
Intent: Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype. Reference: https://refactoring.guru/design-patterns/prototype
Problem: You need to create many objects similar to an existing one. Constructing from scratch is expensive or requires data not accessible at creation time.
Solution: Have objects implement a clone() method that produces a deep copy of themselves.
from copy import deepcopy
from typing import Any
class Prototype:
def clone(self) -> "Prototype":
return deepcopy(self)
class Document(Prototype):
def __init__(self, title: str, content: dict, formatting: dict):
self.title = title
self.content = content
self.formatting = formatting
def __repr__(self):
return f"Document(title={self.title!r}, content={self.content})"
template = Document(
title="Template",
content={"sections": ["intro", "body", "conclusion"]},
formatting={"font": "Arial", "size": 12},
)
# Each report starts from the template, then customizes
report1 = template.clone()
report1.title = "Q1 Report"
report1.content["sections"][0] = "Q1 intro"
report2 = template.clone()
report2.title = "Q2 Report"
assert report1.content is not report2.content # truly independent
When to use:
- Object creation is expensive (parsing config, opening connections, computing initial state).
- You need objects whose exact class isn’t known at compile time.
- You want to avoid the overhead of factories for simple variations.
Pythonic notes:
copy.deepcopyis the standard implementation.- Watch for non-copyable members (file handles, locks, threads); implement
__deepcopy__if needed. - For dataclasses:
dataclasses.replace(obj, **changes)returns a modified copy.
Common pitfalls:
- Shallow vs deep copy confusion — see ../02_python_core/17_shallow_vs_deep_copy.md.
- Cloning objects with non-clonable state (open sockets, threads).
- Forgetting that
clone()returns a new instance; treating it like a reference.
Choosing among creational patterns
| Situation | Pattern |
|---|---|
| One instance, app-wide | Singleton (or just use a module) |
| Subclass decides which object to create | Factory Method |
| Family of related products, internally consistent | Abstract Factory |
| Complex construction with many options | Builder |
| Copy expensive-to-construct objects | Prototype |
When you don’t need any of these — a plain Foo(...) constructor or a top-level def create_foo() function is simpler. Don’t apply patterns dogmatically.
Common interview confusions
- “Factory Method and Abstract Factory are the same.” — Factory Method: subclassing to vary ONE product. Abstract Factory: composition to vary a FAMILY of products.
- “Singleton is always bad.” — global state is risky, but well-bounded use cases exist (logger, config). The anti-Singleton stance is about over-application, not literal prohibition.
- “Builder is for immutable objects.” — not specifically; Builder is for step-by-step construction. Often used with immutable results, but mutable result objects work fine too.
Interview angle
- “What are the GoF creational patterns?” — five patterns dealing with object creation: Singleton (one instance), Factory Method (subclass-decided), Abstract Factory (family of products), Builder (step-by-step complex construction), Prototype (clone existing).
- “Factory Method vs Abstract Factory?” — Factory Method creates one product, with variation via subclassing. Abstract Factory creates a family of related products, with variation via concrete factory implementations.
- “How do you implement Singleton in Python? What’s the Pythonic alternative?” —
__new__with class-level_instance(plus thread-safety lock if needed). Pythonic alternative: a module-level variable (modules are imported once) or dependency injection. - “When is Builder overkill?” — when constructor + dataclass with defaults suffices. Builder pays off with many optional args, mid-construction validation, or fluent chaining significantly improves readability.
- “What does Prototype solve?” — expensive-to-construct objects you need many of. Clone an existing instance instead of running the full constructor.
copy.deepcopyis the standard implementation in Python. - “Why is Singleton hard to test?” — global state persists across tests; one test’s mutation affects the next. Hidden dependency on the Singleton instance makes mocking awkward. Prefer DI for testability.
- “Pool pattern — is it GoF?” — no. It’s an object-pool pattern (Fowler / others) often grouped with creational patterns informally. Useful for expensive resources (DB connections); but not in the original 23.