Factory Method is probably the most misunderstood pattern in the book. People often think it’s about getting rid of new, or getting rid of a switch. It’s really about something else: a base class that runs a workflow, and subclasses that decide which object that workflow uses. CornerStore’s payment processors show exactly that, including what the pattern does not fix.
| Category | Creational |
| Smell fixed | S03: Checkout constructs payment processors in a switch |
| Code | patterns/creational/factory-method |
The Scene
CornerStore takes three payment types. Across the trading day, Ava pays by card and later with Afterpay, and Jo in Auckland pays with PayPal:
[066] checkout customer=cu-4 payment=paypal
PayPal charged NZ$41.63 for Jo
Every checkout has to turn the text "paypal" into an object that can actually charge someone. The question is: whose job is that?
The Smell
In the baseline, it’s the checkout code’s job. Right in the middle of StoreConsole:
// SMELL S03: payment processor construction is selected by a switch in checkout
private static bool ChargePayment(string paymentType, Customer customer, decimal amount)
{
IPaymentProcessor? processor = null;
switch (paymentType)
{
case "card":
processor = new CardProcessor();
break;
case "paypal":
processor = new PayPalProcessor();
break;
case "afterpay":
processor = new AfterpayProcessor();
break;
}
if (processor is null)
{
return false;
}
processor.Charge(customer, amount);
return true;
}
Checkout knows every concrete payment class by name, and it owns the charge workflow: create, check, charge. Those are two different responsibilities tangled together. When you add a payment type, or want every charge to do something extra, you’re editing checkout.
The Idea
Factory Method splits that apart. A creator base class owns the workflow. Inside that workflow, it calls one abstract method to create the product it needs. Each subclass overrides only that method.
Think of a franchise coffee chain. Head office writes the process every store follows: take the order, make the drink, serve it. But each store decides which coffee machine it actually uses. The process never changes. Only the “which machine” step does.
The Refactor
Here’s the creator. It owns the workflow, and leaves exactly one step open:
internal abstract class PaymentProcessorCreator
{
public void Charge(Customer customer, decimal amount)
{
CreateProcessor().Charge(customer, amount);
}
protected abstract IPaymentProcessor CreateProcessor();
}
CreateProcessor() is the factory method. Each concrete creator fills it in:
internal sealed class CardProcessorCreator : PaymentProcessorCreator
{
protected override IPaymentProcessor CreateProcessor()
{
return new CardProcessor();
}
}
And checkout now picks a creator rather than building a processor:
case "card":
creator = new CardProcessorCreator();
break;
case "paypal":
creator = new PayPalProcessorCreator();
break;
Here’s the shape:
classDiagram
class StoreConsole
class PaymentProcessorCreator {
<<abstract>>
+Charge(customer, amount)
#CreateProcessor() IPaymentProcessor
}
class CardProcessorCreator
class PayPalProcessorCreator
class AfterpayProcessorCreator
class IPaymentProcessor {
<<interface>>
+Charge(customer, amount)
}
class CardProcessor
class PayPalProcessor
class AfterpayProcessor
StoreConsole ..> PaymentProcessorCreator : selects
PaymentProcessorCreator <|-- CardProcessorCreator
PaymentProcessorCreator <|-- PayPalProcessorCreator
PaymentProcessorCreator <|-- AfterpayProcessorCreator
PaymentProcessorCreator ..> IPaymentProcessor : creates and uses
IPaymentProcessor <|.. CardProcessor
IPaymentProcessor <|.. PayPalProcessor
IPaymentProcessor <|.. AfterpayProcessor
“Wait, the switch is still there”
Yes, it is. And that’s the most useful thing this example teaches.
Factory Method doesn’t decide which creator to use. Something still has to map "paypal" to PayPalProcessorCreator, and here that’s still a switch in the console. What changed is everything after that choice. The charge workflow lives in one place, in the base class. If tomorrow every charge needs logging, a retry or a receipt, you add it once to PaymentProcessorCreator.Charge, and every payment type gets it.
In a real application, the remaining switch would typically become a dictionary or a dependency injection lookup. That’s a separate decision, and keeping it separate is exactly the “one pattern, one seam” rule of this series.
What It Cost
7 files changed: about 119 lines added and 7 removed. We gained an abstract creator and three tiny concrete creators, so there are now two parallel class hierarchies (creators and processors) that grow together.
That parallel growth is the classic cost of Factory Method. Every new payment type now means two new classes.
When Not to Use It
- There’s no shared workflow. If the creator would only do
return new X(), you’ve built a hierarchy around a constructor call. A simple factory function or a dictionary is clearer. - You only need to pick an implementation. If the goal is just “give me the right processor”, dependency injection or a keyed lookup does that without subclassing.
- The hierarchies won’t grow. Three payment types that rarely change might not justify six classes.
Factory Method shines in frameworks: a base class runs a fixed process and lets you plug in the object it works with by overriding one method.
Easily Confused With
Abstract Factory. Factory Method creates one product through an overridable method in a creator hierarchy. Abstract Factory creates a family of matching products through a factory object. The two even look similar in CornerStore: compare the payment creators here with the region factories in Abstract Factory. The side-by-side is in Factory Method vs Abstract Factory.
Try It Yourself
dotnet build patterns/creational/factory-method/FactoryMethod.slnx
dotnet test patterns/creational/factory-method/FactoryMethod.slnx
dotnet run --project patterns/creational/factory-method/src/CornerStore/CornerStore.csproj -- scenarios/trading-day.json
Try this: add a line to PaymentProcessorCreator.Charge that logs before charging. One edit, and all three payment types pick it up. That’s the pattern earning its keep.
The Factory Method README has the participant table and a sequence diagram. The full change is in DIFF.md.