The Factory pattern — deciding which class to instantiate
When the caller shouldn't need to know which concrete class to construct, a factory is the pattern that moves that decision somewhere else — and it composes naturally with Strategy and Observer, not against them.
3 min read
The problem: construction logic leaking into every caller
def create_shipping_label(method, package):
if method == "standard":
handler = StandardShipping(package)
elif method == "express":
handler = ExpressShipping(package)
elif method == "overnight":
handler = OvernightShipping(package)
else:
raise ValueError(f"unknown shipping method: {method}")
return handler.generate_label()If ten different call sites all need to turn a method string into the right shipping class, this if/elif chain — or something equivalent to it — gets duplicated ten times, and adding SameDayShipping means finding and editing every one of them. This is the same "if/elif choosing between variants" shape the Strategy pattern lesson covers, but the fix here is different: the problem isn't the behavior varying, it's the construction varying — deciding which class to build.
The fix: centralize "which class" in one place
class ShippingFactory:
_handlers = {
"standard": StandardShipping,
"express": ExpressShipping,
"overnight": OvernightShipping,
}
@classmethod
def create(cls, method, package):
handler_class = cls._handlers.get(method)
if handler_class is None:
raise ValueError(f"unknown shipping method: {method}")
return handler_class(package)
handler = ShippingFactory.create("express", package)
handler.generate_label()Every call site now says ShippingFactory.create(method, package) instead of repeating the branch. Adding SameDayShipping means adding one entry to _handlers in exactly one place — every existing call site is already correct and untouched, which is the same Open/Closed benefit the Strategy pattern provides, applied to object creation instead of object behavior.
Factory Method: subclasses decide, not a lookup table
A related but distinct shape: instead of one factory branching on a parameter, each subclass overrides a method that decides what to construct:
class NotificationSender(ABC):
@abstractmethod
def create_channel(self):
... # subclasses decide what kind of channel this is
def notify(self, message):
channel = self.create_channel()
channel.send(message)
class EmailNotificationSender(NotificationSender):
def create_channel(self):
return EmailChannel()
class SmsNotificationSender(NotificationSender):
def create_channel(self):
return SmsChannel()notify() is written once, entirely in terms of create_channel(), and never needs to know which concrete channel it's actually using. This is Factory Method specifically — the "factory" is a method that subclasses override, not a standalone class with a lookup table, which is what plain "Factory" (sometimes called "Simple Factory") usually refers to.
Why factories compose with Strategy instead of replacing it
A factory decides which strategy object to construct; Strategy is about what that object does once you have it. They solve adjacent but different problems, and using them together is normal, not redundant:
handler = ShippingFactory.create(method, package) # Factory: which class
label = handler.generate_label() # Strategy: what it doesConfusing them is a common mistake: reaching for Strategy when the actual problem is "which class do I build" produces an awkward strategy whose only job is picking another strategy; reaching for Factory when the real problem is "this object needs to behave differently at runtime" produces a factory that's really hiding a conditional that belongs in the calling code.
The concrete signal a Factory belongs somewhere
The tell is construction logic — an if/elif or match chain whose entire body is return SomeClass(args) for a different class per branch — duplicated across more than one call site. A single call site with one construction branch usually doesn't need the extra indirection; the pattern earns its keep once the same "which class" decision has to be made correctly in more than one place.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What problem does ShippingFactory.create() solve that repeating the if/elif chain at ten call sites doesn't?
2. How does Factory Method differ from a plain Factory with a lookup table?
3. Why do Factory and Strategy compose together rather than compete?
4. What's the concrete signal that a Factory belongs somewhere in a codebase?