Chapter 3: Creational Patterns
Creational patterns control how objects come into existence. They separate the act of making things from the act of using them; this reduces duplication and keeps construction choices flexible. The following sections introduce the classic set with short pseudocode and compact Python demonstrations.
Singleton
Singleton ensures that only one instance of a class exists and that all clients share it. The goal is a single point of access for shared state that truly must be global.
Intent
Provide exactly one object that coordinates or stores shared resources; give all callers a stable access path.
Structure
A class hides its constructor and exposes a get_instance or similar function that returns the single object. Lazy creation is common; the instance appears at first use.
Pseudocode
class Singleton {
private static instance
static get_instance() {
if instance is null then
instance = new Singleton()
return instance
}
method use() { … }
}
The pseudocode omits thread safety. Real systems often add a lock or rely on language features that guarantee safe initialisation.
Python
class _Singleton:
def __init__(self):
self.settings = {}
_singleton = None
def get_instance():
global _singleton
if _singleton is None:
_singleton = _Singleton()
return _singleton
When to Use
Use Singleton for configuration, logging or a resource manager that must be unique and shared.
Tradeoffs
Global access can hide dependencies; testing becomes harder if code reaches into the singleton directly. Prefer explicit dependency passing when practical.
Factory Method
Factory Method delegates construction to subclasses or collaborator objects. Callers ask for a product through a common method; the factory chooses which concrete class to create.
Intent
Define a method that returns a Product; let subclasses decide which concrete product appears.
Structure
An abstract creator declares create_product. Concrete creators override it to build specific products. Clients work with the Product interface and remain unaware of the concrete type.
Pseudocode
interface Product { method run() }
class ConcreteA implements Product { method run() { … } }
class ConcreteB implements Product { method run() { … } }
abstract class Creator {
abstract method create_product() : Product
method operate() {
p = this.create_product()
p.run()
}
}
class CreatorA extends Creator { method create_product() { return new ConcreteA() } }
class CreatorB extends Creator { method create_product() { return new ConcreteB() } }
Python
class Product:
def run(self):
raise NotImplementedError
class ConcreteA(Product):
def run(self):
return "A"
class ConcreteB(Product):
def run(self):
return "B"
class Creator:
def create_product(self) -> Product:
raise NotImplementedError
def operate(self):
return self.create_product().run()
class CreatorA(Creator):
def create_product(self) -> Product:
return ConcreteA()
class CreatorB(Creator):
def create_product(self) -> Product:
return ConcreteB()
When to Use
Use Factory Method when a class cannot anticipate which product subclass to create or when subclasses should choose.
Tradeoffs
Indirection adds small complexity; you gain flexibility and testability in exchange for extra classes.
Abstract Factory
Abstract Factory creates families of related products that must work together. Clients obtain a factory for a given family; all products from that factory are compatible.
Intent
Provide an interface for creating related objects without specifying concrete classes; ensure consistent combinations.
Structure
The factory interface declares one method per product type, such as make_button and make_window. Concrete factories build themed sets, such as DarkFactory and LightFactory.
Pseudocode
interface UIFactory {
method make_button() : Button
method make_window() : Window
}
class DarkFactory implements UIFactory {
method make_button() { return new DarkButton() }
method make_window() { return new DarkWindow() }
}
class LightFactory implements UIFactory {
method make_button() { return new LightButton() }
method make_window() { return new LightWindow() }
}
Python
class UIFactory:
def make_button(self):
raise NotImplementedError
def make_window(self):
raise NotImplementedError
class DarkFactory(UIFactory):
def make_button(self):
return {"type": "button", "theme": "dark"}
def make_window(self):
return {"type": "window", "theme": "dark"}
class LightFactory(UIFactory):
def make_button(self):
return {"type": "button", "theme": "light"}
def make_window(self):
return {"type": "window", "theme": "light"}
When to Use
Use Abstract Factory when products must be used together and you want to enforce valid combinations.
Tradeoffs
Adding a new product type means changing every concrete factory; adding a new family is simpler because you implement one new factory.
Builder
Builder constructs complex objects step by step. It separates the process of building from the final representation; this lets the same construction steps create different forms.
Intent
Encapsulate construction logic as a sequence of operations; allow different builders to produce varied results from the same process.
Structure
A director coordinates steps such as add_part and set_option. The builder records choices and finally returns a finished product with build or get_result.
Pseudocode
class Builder {
method reset()
method add_part(x)
method set_option(k, v)
method get_result() : Product
}
class Director {
method construct(b : Builder) {
b.reset()
b.add_part("core")
b.set_option("mode", "fast")
}
}
Python
class ReportBuilder:
def reset(self):
self._title = None
self._lines = []
def add_part(self, line):
self._lines.append(line)
def set_option(self, k, v):
if k == "title":
self._title = v
def get_result(self):
return {"title": self._title, "body": "\n".join(self._lines)}
class Director:
def construct(self, b: ReportBuilder):
b.reset()
b.set_option("title", "Summary")
b.add_part("Line 1")
b.add_part("Line 2")
When to Use
Use Builder when creation needs many steps or when different configurations must share a common assembly process.
Tradeoffs
You gain clarity for complex construction; you pay with extra classes and one more layer of calls.
Prototype
Prototype creates new objects by copying existing ones. Instead of building from scratch, clients clone a ready instance and adjust a few fields.
Intent
Start from an example object; duplicate it quickly; customise the copy.
Structure
Each prototype supports a clone operation that returns a new instance with the same internal state. Cloning may be shallow or deep; choose based on whether nested structures must be copied.
Pseudocode
interface Prototype { method clone() : Prototype }
class Document implements Prototype {
fields: title, parts
method clone() {
copy = new Document()
copy.title = this.title
copy.parts = this.parts.copy() // deep copy as needed
return copy
}
}
Python
import copy
class Document:
def __init__(self, title, parts):
self.title = title
self.parts = list(parts)
def clone(self):
return Document(self.title, copy.deepcopy(self.parts))
copy.copy for shallow clones and copy.deepcopy for deep clones; choose based on how nested your state is.
When to Use
Use Prototype when object creation is expensive or when many similar objects share mostly the same setup.
Tradeoffs
Cloning can duplicate unwanted links if you choose shallow copy where deep copy is required; copying large graphs may also be costly at runtime.