Factory method delegates 'which subclass to build' to one place; Abstract Factory does the same for entire families of related objects. Built through a NotificationFactory exercise.
Published September 22, 2026
newThe problem Factory solves: object-creation logic (which concrete class to instantiate, based on some condition) tends to get copy-pasted at every call site that needs one, and every new type means hunting down and editing every one of those call sites.
interface Notifier { void send(String to, String message); }
class EmailNotifier implements Notifier { public void send(String to, String message) { /* ... */ } }
class SmsNotifier implements Notifier { public void send(String to, String message) { /* ... */ } }
class PushNotifier implements Notifier { public void send(String to, String message) { /* ... */ } }
class NotifierFactory {
static Notifier create(String type) {
return switch (type) {
case "EMAIL" -> new EmailNotifier();
case "SMS" -> new SmsNotifier();
case "PUSH" -> new PushNotifier();
default -> throw new IllegalArgumentException("Unknown type: " + type);
};
}
}
Callers depend only on NotifierFactory.create(type) and the Notifier interface — never on a concrete class directly. Adding a new channel means editing the factory in one place, not hunting down every new EmailNotifier() scattered through the codebase. Note this is still technically a single point of if/switch on type — it doesn't eliminate branching on type entirely (that's what Strategy + polymorphism does more fully), but it does centralize it to exactly one place instead of many.
Factory Method produces one type of object. Abstract Factory produces a family of related objects that need to stay consistent with each other.
interface Button { void render(); }
interface Checkbox { void render(); }
// Family 1: Windows-style widgets
class WindowsButton implements Button { public void render() { /* Windows look */ } }
class WindowsCheckbox implements Checkbox { public void render() { /* Windows look */ } }
// Family 2: Mac-style widgets
class MacButton implements Button { public void render() { /* Mac look */ } }
class MacCheckbox implements Checkbox { public void render() { /* Mac look */ } }
interface UIFactory {
Button createButton();
Checkbox createCheckbox();
}
class WindowsUIFactory implements UIFactory {
public Button createButton() { return new WindowsButton(); }
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
class MacUIFactory implements UIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
The guarantee Abstract Factory provides that plain Factory Method doesn't: given one UIFactory implementation, every widget it produces belongs to the same family — code using a WindowsUIFactory can never accidentally end up with a MacCheckbox next to a WindowsButton, because the factory itself is the single source of truth for which family is active.
The example above already builds this, but worth stating the design decision explicitly: is this Factory Method or Abstract Factory? It's Factory Method — Notifier is a single interface, and the factory just picks which concrete implementation to hand back based on a type parameter. It would become Abstract Factory only if, say, each notification channel needed a family of related objects (a Notifier and a matching MessageFormatter and a matching DeliveryTracker, all required to be the same "brand" for a given channel) — at that point, one factory method per family member, grouped behind one NotificationFactory interface, is the Abstract Factory shape.
Q: Isn't Factory Method just an if/switch statement with extra steps?
A: The value isn't eliminating the conditional — it's centralizing it. Every call site that needs a Notifier depends on the factory and the interface, never on a concrete class; the conditional exists in exactly one place instead of being copy-pasted at every construction site, which is what actually makes adding a new type a one-file change.
Q: When would you reach for Abstract Factory over Factory Method? A: When objects genuinely need to be created in matching sets that must stay internally consistent — cross-platform UI toolkits (the classic example), or a multi-tenant system where every object created for a given tenant must come from that tenant's specific configuration family.
Q: How does Factory Method relate to Dependency Inversion? A: They solve different problems that often appear together — DIP is about a consumer depending on an abstraction rather than a concrete class; Factory Method is about centralizing the decision of which concrete class implements that abstraction. A DIP-compliant class typically receives its dependency through the constructor, having been built by a factory (or a DI container acting as one) somewhere upstream.
Q: Can a Spring @Bean method be considered a Factory Method?
A: Functionally, yes — a @Bean method is exactly a factory: it decides which concrete implementation to construct and hand back, and Spring's container acts as the caller requesting an instance by type, rather than any application code calling new directly.