1 Overview
In object-oriented programming (OOP), instantiation is rarely just a call to new
. As a code-base grows, dependencies become tangled and requirements shift. Creational Patterns hide construction details, provide controlled extension points, and reduce coupling between client code and concrete classes.
Pattern | Core Idea | Typical Use Cases |
---|---|---|
Singleton | Guarantee exactly one instance and offer global access | Configuration, DB connection pool, logging |
Factory Method | Delegate creation to sub-classes | Frameworks where plug-ins decide the product type |
Abstract Factory | Produce a family of related objects | Cross-platform UI widgets, multi-DB drivers |
Builder | Separate construction steps from representation | Complex objects with many optional parts |
Prototype | Clone an existing object instead of rebuilding | Expensive setup, repeated configurations |
2 Pattern Deep-Dive
2.1 Singleton
1. Definition
Ensure a single object of a class exists and provide a global access point to it.
2. Problem Solved
- Resources that must remain unique: config, loggers, caches.
- Prevent wasted memory or inconsistent state from multiple instances.
3. How It Works
- Private constructor (or guarded
__new__
). - Store the unique instance in a static field.
- Expose a public accessor such as
getInstance()
.
4. Pros / Cons
Pros | Cons |
---|---|
Saves resources, keeps state consistent | Hinders unit tests, tightly couples code |
Easy to reach from anywhere | Can be over-used → “God object” |
Lazy or eager initialization possible | Must be thread-safe in multithreaded apps |
5. Example (Python)
from threading import Lock
class Logger:
_instance = None
_lock = Lock()
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
if cls._instance is None: # double-checked
cls._instance = super().__new__(cls)
cls._instance._setup()
return cls._instance
def _setup(self):
self._file = open("app.log", "a")
def log(self, msg: str):
self._file.write(msg + "\n")
2.2 Factory Method
1. Definition
Define an interface for creating an object, but let sub-classes decide which concrete class to instantiate.
2. Problem Solved
A framework cannot foresee every concrete class a plug-in might need, yet wants a standard way to obtain objects.
3. How It Works
- A
Creator
declaresfactory_method() → Product
. - Each
ConcreteCreator
overrides it to return its ownConcreteProduct
.
4. Pros / Cons
Pros | Cons |
---|---|
Open/Closed: add products without changing core | More classes to maintain |
Removes dependency on concrete classes | Creation logic scattered across sub-classes |
5. Example (Python)
class Document: ...
class PdfDocument(Document): ...
class WordDocument(Document): ...
class Application:
def create_document(self) -> Document: # Factory Method
raise NotImplementedError
def open(self):
doc = self.create_document()
print(f"Opened {doc.__class__.__name__}")
class PdfApp(Application):
def create_document(self):
return PdfDocument()
class WordApp(Application):
def create_document(self):
return WordDocument()
app = PdfApp()
app.open() # → Opened PdfDocument
2.3 Abstract Factory
1. Definition
Provide an interface for creating entire families of related objects without specifying their concrete classes.
2. Problem Solved
- Cross-platform GUI: WindowsButton vs. MacButton, yet client code should stay platform-agnostic.
- Keep products within the same family compatible.
3. How It Works
AbstractFactory
declares methods likecreate_button()
andcreate_scrollbar()
.ConcreteFactoryWindows
/ConcreteFactoryMac
implement them, returning platform-specific widgets.
4. Pros / Cons
Pros | Cons |
---|---|
Ensures product consistency | Harder to add a new product type (every factory changes) |
Swap whole families easily | Increases class count |
Insulates client from concrete classes |
5. Example (Python)
class Button: ...
class ScrollBar: ...
class WindowsButton(Button): ...
class MacButton(Button): ...
class AbstractFactory:
def create_button(self) -> Button: ...
def create_scrollbar(self) -> ScrollBar: ...
class WindowsFactory(AbstractFactory):
def create_button(self): return WindowsButton()
def create_scrollbar(self): ...
class MacFactory(AbstractFactory):
def create_button(self): return MacButton()
# ...
def render_ui(factory: AbstractFactory):
btn = factory.create_button()
sb = factory.create_scrollbar()
# use btn and sb
factory = WindowsFactory() if platform == "win32" else MacFactory()
render_ui(factory)
2.4 Builder
1. Definition
Separate the construction process of a complex object from its final representation so the same steps can yield different results.
2. Problem Solved
- Objects with many optional parameters lead to telescoping constructors.
- Need to reuse an assembly sequence across varied configurations.
3. How It Works
Director
orchestrates build steps.Builder
defines steps (add_cheese
,add_sauce
, …).ConcreteBuilder
stores interim state and emits the product viabuild()
.
4. Pros / Cons
Pros | Cons |
---|---|
Produces immutable finished objects | Added complexity for simple objects |
Fluent, readable creation syntax | Requires extra Builder/Director classes |
Same algorithm → multiple variants |
5. Example (Python, fluent style)
class Burger:
def __init__(self, size, cheese=False, bacon=False):
self.size, self.cheese, self.bacon = size, cheese, bacon
def __repr__(self):
return f"Burger(size={self.size}, cheese={self.cheese}, bacon={self.bacon})"
class BurgerBuilder:
def __init__(self): self.reset()
def reset(self): self._size = 1; self._cheese = False; self._bacon = False; return self
def size(self, s): self._size = s; return self
def cheese(self): self._cheese = True; return self
def bacon(self): self._bacon = True; return self
def build(self): return Burger(self._size, self._cheese, self._bacon)
burger = BurgerBuilder().size(2).cheese().bacon().build()
# → Burger(size=2, cheese=True, bacon=True)
2.5 Prototype
1. Definition
Create new objects by cloning an existing prototype, rather than instantiating from scratch.
2. Problem Solved
- Initialization is costly (I/O, validation, network).
- Desire to duplicate complex configuration quickly.
3. How It Works
- Prototype implements
clone()
(shallow or deep). - Client calls
clone()
to obtain a copy and may tweak it.
4. Pros / Cons
Pros | Cons |
---|---|
Faster than re-initialization | Must ensure deep copy to avoid shared state |
Preserve intricate config, then diverge | Managing prototype lifecycle |
Removes dependence on concrete class |
5. Example (Python)
import copy
class Shape:
def __init__(self, color): self.color = color
def clone(self):
return copy.deepcopy(self)
circle = Shape("red")
blue_circle = circle.clone()
blue_circle.color = "blue"
3 Conclusion
Creational Patterns are foundational:
- Open/Close Gatekeepers – conceal construction logic, yet stay open to new products.
- Separation of Concerns – distinguish what the client wants from how it is built.
- Maintainability & Testability – enable dependency inversion, mocking, and clean architecture.
- Scalability – frameworks, plug-ins, and micro-services routinely combine multiple creational patterns to stay tractable over time.