LESSON 58 · ADVANCED4 min read

Creational Patterns

Control object creation with factories, builders, and module-level singletons without hiding dependencies.

Python 3.12+Free to readModule 13 of 19
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Choose a factory when construction varies.
  • Use a builder for multi-step configuration.
  • Recognize when a module already provides singleton behavior.
  • Keep creation testable and explicit.

01Patterns are vocabulary, not templates

A design pattern names a recurring solution to a design problem. The name is useful in conversation, but the code should be justified by the problem, not by a desire to use the pattern. Python often has a smaller expression for the same idea: a function instead of a factory class, a module instead of a singleton, a dataclass instead of a builder.

Start with the simplest construction that works. Introduce a pattern when construction logic varies independently of the caller, when a complex object has many optional parts, or when a creation decision must be centralized. Then write the smallest implementation that captures that decision.

simple_creation.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Factories centralize a choice

A factory receives the information that determines which object to create and returns a fully initialized object. A registry maps names to constructors, which makes adding a new type a local change instead of editing a long if/elif chain. The caller depends on the returned interface, not on the concrete class.

Keep the registry explicit and validate unknown keys. A factory that silently returns None for a typo turns a configuration mistake into a confusing None error much later. Raise a clear ValueError or define a domain-specific configuration error.

notification_factory.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03A builder tames many options

A builder separates the construction steps from the final object. It is useful when an object has many optional settings and a single constructor call would be an unreadable list of positional arguments. A fluent builder returns self from each setter so calls can chain.

A builder is not an excuse to skip validation. build() should produce a valid object or raise a clear error. For small records, a dataclass with defaults is simpler. Reach for a builder when the construction itself has stages, defaults that depend on other choices, or a need to reuse a partial configuration.

request_builder.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

04A module is often the singleton

A singleton promises one shared instance. Python modules are imported once per process, so module-level state already provides a natural shared instance. A class-based singleton with __new__ adds global state and makes tests order-dependent; pass the shared object into the code that needs it instead.

If a shared resource must be initialized lazily, expose a get_client() function with a clear lifecycle rather than scattering a global instance. Document reset hooks for tests. The pattern should make ownership clearer, not merely reduce the number of constructor calls.

module_singleton.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
LET IT CLICK

The ideas worth keeping.

  • Use a factory when the choice of implementation varies.
  • Use a builder when construction has stages or many optional parts.
  • Prefer a module or injected collaborator over a global singleton.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Build a notifier registry

Create a factory that accepts 'email' or 'sms' and returns an object with send(message). Raise ValueError for an unknown key.

your_turn_1.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
class Email:    def send(self, message): return f"email: {message}"class Sms:    def send(self, message): return f"sms: {message}"REGISTRY = {"email": Email, "sms": Sms}def create(channel):    if channel not in REGISTRY:        raise ValueError(f"unknown channel: {channel}")    return REGISTRY[channel]()print(create("email").send("hello"))

The registry centralizes the mapping and the validation. Adding a channel means adding one entry, while callers keep using the same send contract.

02

Build an HTTP request

Use a fluent builder to create a GET request with an accept header and a 5-second timeout. Validate that the URL starts with https://.

your_turn_2.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
class Request:    def __init__(self, url, method, headers, timeout):        self.url, self.method = url, method        self.headers, self.timeout = headers, timeoutclass Builder:    def __init__(self, url):        self.url, self.method, self.headers, self.timeout = url, "GET", {}, 10    def method(self, value):        self.method = value        return self    def header(self, key, value):        self.headers[key] = value        return self    def build(self):        if not self.url.startswith("https://"):            raise ValueError("https required")        return Request(self.url, self.method, self.headers, self.timeout)request = Builder("https://example.com").header("accept", "json").build()print(request.headers["accept"], request.timeout)

The fluent methods return the builder, so the call reads as a sequence of choices. build() is the single validation point before the immutable request is used.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01When is a factory most useful?
02Why is a module often a better singleton than a class with __new__?
03What should build() do when required configuration is missing?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Build a notifier registry

Create a factory that accepts 'email' or 'sms' and returns an object with send(message). Raise ValueError for an unknown key.

EXPECTED OUTPUT
create("email").send("hello")
main.pyPython 3
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.