The Factory Method Pattern
Shelfwise charges Indian customers through a UPI gateway and everyone else by card. The same three lines, if country == "IN": gateway = UpiGateway() else: gateway = CardGateway(), appear in checkout, refunds and subscriptions. Adding a wallet option for one country means finding and editing all three, and hoping nobody made a fourth copy. Factory Method gives that decision one home.
The rule
Put "which class do I create?" in one method that returns the abstraction.
Callers want a PaymentGateway. They should not have to know which concrete class to build, or the rule for choosing it. A factory method holds that knowledge:
def gateway_for(country) -> PaymentGateway:
if country == "IN":
return UpiGateway()
return CardGateway()
gateway = gateway_for(order.country)
gateway.charge(order.payment_details, order.total())Checkout, refunds and subscriptions each call gateway_for(...) and then use only the PaymentGateway interface. None of them names UpiGateway or CardGateway. Adding a WalletGateway means writing that class and adding one line inside gateway_for; no caller changes.
In the classic object-oriented form, the factory method is a method on a base class that subclasses override to decide what gets made:
class ReportExporter(ABC):
def export(self, figures):
writer = self.create_writer() # the factory method
writer.write(figures)
@abstractmethod
def create_writer(self): ...
class CsvExporter(ReportExporter):
def create_writer(self):
return CsvWriter()export holds the shared steps; each subclass decides which writer to create. Both forms share the same idea: creation is separated from use, and callers depend only on the abstraction.
It pairs naturally with Dependency Inversion. DIP says checkout should receive an abstraction; a factory is often the place that decides which concrete one to hand over.
The shortcut
Search for the same if choosing which class to construct in more than one place. Move it into one function or method named for what it produces, make it return the abstraction, and replace every copy with a call.
Spot the repeated choice
Three files contain
if customer.prefers_sms: SmsSender() else: EmailSender().
The trap is wrapping every constructor in a factory. make_book(title, price) that just calls Book(title, price) adds a layer and hides nothing. A factory earns its place when there is a choice to hide.
Drills
Answer fast — trust the rule.
What should a factory method return?
After introducing gateway_for(country), what does adding WalletGateway require?
In the subclass form, what does CsvExporter override?
When is a factory method NOT worth adding?
Remember
- Put "which class do I create?" in one method that returns the abstraction.
- Repeated class-choosing
ifs become one factory, and every copy becomes a call. - Trap: factories that wrap a single constructor and hide no choice.
