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.

Intermediate

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 does

Confusing 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?