Factory
The Factory pattern centralizes object creation. Instead of calling new SomeConcreteClass()
at many points in the codebase, callers ask a single factory method or class for an object and
receive a reference typed to a shared interface. The factory holds the logic that decides which
concrete class to instantiate.
Consider a notification system that supports email, SMS, and push channels. Without a factory,
new EmailNotification(), new SmsNotification(), and new PushNotification() appear across
controllers, scheduled jobs, event handlers, and retry workers. When a fourth channel is added,
or when EmailNotification gains a new constructor parameter, every one of those call sites
must be updated.
The underlying issue is that the decision of which class to instantiate is distributed across the codebase rather than owned by one location.
How the Factory solves it
A factory is a single method or class that owns the new calls. The rest of the codebase asks
for a notification and the factory decides which concrete type to build. Every call site becomes
identical — factory.create(channel) — and the switching logic lives in exactly one location.
The factory method returns the abstract Notification type. None of the callers import
EmailNotification directly — they only know about the interface. Add a fourth channel by
adding one branch in the factory and one new class; nothing else changes.
At runtime, the caller asks the factory for a notification and then uses it without ever knowing the concrete type that was created.
Simple Factory vs. Factory Method vs. Abstract Factory
These three names appear in interviews and often get conflated.
The Simple Factory is a single class with a single create method that contains a
conditional. It is not a Gang-of-Four pattern; it is a pragmatic refactoring. The cost is a
mild violation of the Open/Closed Principle: adding a new product requires editing the factory
class rather than extending it. For most self-contained systems that cost is acceptable.
The GoF Factory Method pattern takes a different approach. An abstract creator class
declares a createProduct() method that returns the product interface, and concrete subclasses
override that method to decide which product to build. The canonical example is a Dialog
base class with createButton() — WindowsDialog overrides it to return a WindowsButton;
WebDialog overrides it to return an HtmlButton. The pattern is appropriate when the
creator class itself is being subclassed and each subclass naturally pairs with a specific
product family.
The Abstract Factory pattern goes one step further: a factory interface produces an entire
family of related products that must be compatible with each other. A GUIFactory interface
might declare createButton() and createCheckbox(); a WindowsFactory implementation
returns a WindowsButton and a WindowsCheckbox that share a visual style, while a
MacFactory returns matching Mac counterparts. The constraint being enforced is consistency
across a product family — you should not mix a Windows button with a Mac checkbox.
| Variant | Structure | Use when |
|---|---|---|
| Simple Factory | One class, one method, a conditional | Centralising object creation; no inheritance required |
| Factory Method (GoF) | Abstract creator subclassed per product | The creator itself is extended and each subclass owns one product |
| Abstract Factory (GoF) | Interface producing a family of products | Multiple related objects must stay mutually compatible |
Both variants are worth knowing by name. The Simple Factory is the more common choice unless the problem explicitly calls for inheritance-based variation.
Bad → Good
Without the factory, every call site mixes instantiation with business logic.
1# Bad: instantiation decision is duplicated at every call site.
2# Adding a new channel means hunting through the whole codebase.
3def send_order_confirmation(channel: str, order_id: str, email: str) -> None:
4 if channel == "email":
5 notif = EmailNotification(smtp_host="mail.example.com")
6 elif channel == "sms":
7 notif = SmsNotification(api_key="key-123")
8 else:
9 raise ValueError(f"unknown channel: {channel}")
10 notif.send(email, f"Order {order_id} confirmed")
11
12# The same if/elif block appears again in send_shipping_update(),
13# send_refund_notice(), and so on — each a place that needs updating.
With a factory, every call site becomes a single-liner. The if/elif block that used to be
duplicated is now owned by NotificationFactory.create.
1from abc import ABC, abstractmethod
2
3class Notification(ABC):
4 @abstractmethod
5 def send(self, recipient: str, message: str) -> None: ...
6
7class EmailNotification(Notification):
8 def __init__(self, smtp_host: str) -> None:
9 self._smtp_host = smtp_host
10 def send(self, recipient: str, message: str) -> None:
11 print(f"[Email via {self._smtp_host}] → {recipient}: {message}")
12
13class SmsNotification(Notification):
14 def __init__(self, api_key: str) -> None:
15 self._api_key = api_key
16 def send(self, recipient: str, message: str) -> None:
17 print(f"[SMS key={self._api_key}] → {recipient}: {message}")
18
19class PushNotification(Notification):
20 def send(self, recipient: str, message: str) -> None:
21 print(f"[Push] → {recipient}: {message}")
22
23# Good: one place owns all instantiation logic.
24class NotificationFactory:
25 def create(self, channel: str) -> Notification:
26 if channel == "email":
27 return EmailNotification(smtp_host="mail.example.com")
28 elif channel == "sms":
29 return SmsNotification(api_key="key-123")
30 elif channel == "push":
31 return PushNotification()
32 raise ValueError(f"unknown channel: {channel}")
33
34# Every call site is now identical — no class names, no constructor args.
35def send_order_confirmation(channel: str, order_id: str, email: str) -> None:
36 factory = NotificationFactory()
37 notif = factory.create(channel)
38 notif.send(email, f"Order {order_id} confirmed")
When NOT to use this
The factory pattern is worth using when you have two or more implementations sharing a common
interface and when the choice between them is made at runtime. If you only ever instantiate one
concrete type — say, your system sends only emails today — a factory is pure ceremony. new EmailNotification(...) is clearer.
Construction is also sometimes trivial enough that extracting a factory class is more work than
it saves. A value object like Money(amount=100, currency="USD") has no conditional, no
shared interface, and no reason for a factory. Over-using the pattern is a common interview
anti-signal: it suggests you applied a name rather than solved a problem.
The factory pattern is applicable when a duplicated if/elif block chooses between concrete
types. Without that duplication, the pattern is likely unnecessary.