Most factory code in the wild is a glorified if-statement with a class name on it. The pattern is worth knowing for the one problem it actually solves: who gets to decide which concrete class exists.
You have a function that charges a card. It started with one provider, and now it looks like this:
def charge(provider, amount):
if provider == "stripe":
return f"stripe charged {amount}"
elif provider == "adyen":
return f"adyen charged {amount}"
raise ValueError(provider)
Nothing is wrong with this. Two branches, one file, a reader understands it in five seconds. The trouble arrives in month eight, when charge is called from the checkout flow, the refund job, the subscription renewer and a back-office script, and the product team adds a third provider. Now you edit four call sites, or you discover that three of them were quietly switching on the provider name in slightly different ways, and one of them forgot the else.
That is the whole motivation for the Factory family. Not elegance. Not "open for extension". One decision, made in one place, so that the rest of the code stops knowing which concrete thing it is holding.
What the GoF actually said
The Gang of Four book (1994) lists two separate creational patterns that people routinely blur together.
Factory Method is an inheritance trick. A base class defines an algorithm and leaves one step, "make the object I need", abstract. Subclasses decide what gets made. The book's phrasing is that it lets a class defer instantiation to subclasses.
Abstract Factory is about families. It gives you an interface for creating a set of related objects, such as a storage client and a queue client that must belong to the same environment, without naming their concrete classes. The point is consistency: you never end up with an S3 storage client wired to an in-memory queue by accident.
What most developers call "a factory" is neither. It is a function, or a static method, that takes a string and returns an object. The GoF book does not have a chapter for it, and that gap is where most of the confusion lives. Joshua Bloch's name for the static version, "static factory method", is the more honest one, and it is the version you will write 90 percent of the time.
Factory Method, in code
Here is the textbook shape, applied to the payments example. The base class owns the workflow. Subclasses own one decision.
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
def checkout(self, amount):
gateway = self.create_gateway() # the factory method
return gateway.charge(amount)
@abstractmethod
def create_gateway(self): ...
class StripeProcessor(PaymentProcessor):
def create_gateway(self):
return StripeGateway()
class AdyenProcessor(PaymentProcessor):
def create_gateway(self):
return AdyenGateway()
I ran this, along with the other snippets in this post, on Python 3. StripeProcessor().checkout(42) prints stripe charged 42, and the Adyen one does the same with its own name. It works.
Now the uncomfortable part. Look at what you paid for this. Two new classes whose only job is to return a single object. A parallel hierarchy that has to grow every time a provider does. And a design where choosing the provider means choosing a subclass at the call site, which moves the if somewhere else instead of removing it.
Factory Method earns its keep in a narrow situation: you have a template algorithm in a framework, and the framework author cannot know which concrete type the user wants. Think of a UI toolkit where a base Application class creates documents, and your subclass decides they are spreadsheets. In application code, where you own both sides, this is usually inheritance used as a very verbose switch statement.
The factory you will actually write
The version that survives code review is a plain function backed by a registry. The decision lives in one dictionary, and adding a provider is one decorated class.
_REGISTRY = {}
def register(name):
def deco(cls):
_REGISTRY[name] = cls
return cls
return deco
def gateway_for(name, **cfg):
try:
return _REGISTRY[name](**cfg)
except KeyError:
raise ValueError(
f"unknown gateway {name!r}, known: {sorted(_REGISTRY)}"
) from None
@register("stripe")
class StripeGateway:
def __init__(self, api_key="test"):
self.api_key = api_key
def charge(self, amount):
return f"stripe charged {amount}"
Calling gateway_for("paypal") fails with unknown gateway 'paypal', known: ['adyen', 'stripe']. That error message is a feature people underrate. A factory is the one place in the system that knows the full list of options, so it is the right place to say what they are.
Notice what the callers lost: the ability to import StripeGateway. That is deliberate. Once call sites ask for a name instead of a class, you can change construction freely. Add retry wrappers, read config, return a cached instance, swap in a test double. None of that touches the caller. This is the entire benefit, and it comes from the indirection, not from any particular class diagram.
Abstract Factory: when objects must match
Abstract Factory looks like overkill until you hit its specific problem: several objects that only make sense together.
Say your service uses blob storage and a message queue. In production those are S3 and SQS. In local development and CI you want in-memory versions. The bug you are guarding against is mixing them: real storage, fake queue, and a test that passes while production behaves differently.
class InfraFactory(ABC):
@abstractmethod
def storage(self) -> Storage: ...
@abstractmethod
def queue(self) -> Queue: ...
class InMemoryInfra(InfraFactory):
def storage(self): return MemStorage()
def queue(self): return MemQueue()
def process(infra: InfraFactory):
s, q = infra.storage(), infra.queue()
s.put("order:1", "paid")
q.push("order:1")
process has no idea whether it is running against AWS or a dictionary. You hand it a family, and the family guarantees its own members are compatible. A real AwsInfra class would return a boto3-backed storage and queue; I have not wired that up here because the shape is the point, not the SDK.
The test for whether you need this: do you have two or more products that vary together along the same axis? If you have one axis (just the payment gateway) you want a registry. If you have three components that all flip between "real" and "fake" at the same moment, you want a factory object that flips them as a unit. If you are not sure, you do not need it.
Where factories already live in your stack
The strongest argument for the pattern is that you rely on it daily without writing it.
Go's database/sql. You call sql.Open("postgres", dsn) with a driver name. The driver registered itself in its init() by calling sql.Register. The standard library keeps a map from name to driver and hands you a *sql.DB without ever importing the Postgres package. This is the registry factory above, built into the language ecosystem, and it is why the blank import (_ "github.com/lib/pq") looks so strange to newcomers: it exists only to run the registration.
SQLAlchemy's create_engine. You pass a URL such as postgresql://... or sqlite://.... The scheme selects a dialect, which selects a family of compatible pieces: type handling, SQL compilation, and the DBAPI connection layer. That is Abstract Factory behavior, hidden behind one function and a string.
Django's storage backends. Since Django 4.2 the STORAGES setting maps names to backend classes, and the framework instantiates the configured one. Swap local disk for an S3 backend in settings and no model code changes. A settings-driven registry factory again.
Python's open(). It returns a text wrapper, a buffered reader, or a raw file object depending on the mode you pass. Same call, different concrete types, and you never name any of them.
The pattern across all four: the caller supplies a name or a mode, and a registry or a dispatch table owns the mapping. Nobody writes class StripeProcessorCreator.
When to skip it
Three situations where a factory makes the code worse.
First, one implementation. If there is a single EmailSender and you are writing EmailSenderFactory "for flexibility", you have bought a file, an interface and an import to protect against a change that has not been requested. When the second sender arrives, the refactor takes twenty minutes. Do it then.
Second, a factory whose only body is return Thing(args). This is a constructor with extra steps. Languages that allow named constructors, like Thing.from_config(...) or Kotlin's companion functions, give you the readability gain without the indirection.
Third, when the real problem is object lifetime or wiring. If your "factory" is deciding what to inject into whom, you are describing dependency injection, which is the next post in this series and a different tool. Factories create; containers compose.
A decision rule
When you reach for this pattern, ask three questions in order.
Do callers need to know the concrete class? If no, and there are at least two classes, a static factory function with a registry or a
matchstatement is enough.Is there a fixed workflow where only one step varies, and are you writing a framework others will subclass? That is the only honest case for Factory Method.
Do several objects need to be swapped as a consistent set? Abstract Factory.
Most real code stops at the first question. The textbook diagrams, with their two parallel inheritance trees, describe the rare answers to the second and third.
The reason this pattern keeps getting taught badly is that the interesting idea is small. A factory is a place where the answer to "what concrete thing do I build?" is written down once. Everything else, the subclasses and the interfaces and the creator hierarchies, is optional ceremony you add only when a second axis of variation shows up and forces your hand.
A note on the code: the Python snippets above were run locally and print the outputs quoted. The Go and framework behavior comes from each project's documentation, and I did not execute that part.

