Factory Method & Abstract Factory
Both patterns address the same root problem: client code should not need to know which concrete class it is instantiating. By hiding the new keyword behind a method or an object, you can swap implementations, add new variants, and test in isolation — all without touching the code that consumes the objects.
Why Object Creation Matters
When a class calls new ConcreteProduct() directly, it is tightly coupled to that implementation. Every place that coupling exists becomes a pain point when requirements change. The Factory patterns move that decision to one well-defined location, satisfying the Open/Closed Principle — open for extension, closed for modification.
Key distinction to hold in your head: Factory Method uses inheritance — a subclass decides what to create. Abstract Factory uses composition — an injected factory object creates families of related objects.
Factory Method
Define an interface for creating an object, but let subclasses decide which class to instantiate. The creator has a method — the "factory method" — declared abstract (or with a default) that subclasses override.
// Product interface
public interface Notification {
void send(String message);
}
// Concrete products
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}
public class SmsNotification implements Notification {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
// Creator — declares the factory method
public abstract class NotificationSender {
// Factory method — subclasses override this
protected abstract Notification createNotification();
// Template that uses the factory method
public void notify(String message) {
Notification n = createNotification();
n.send(message);
}
}
// Concrete creators — one per variant
public class EmailSender extends NotificationSender {
@Override
protected Notification createNotification() {
return new EmailNotification();
}
}
public class SmsSender extends NotificationSender {
@Override
protected Notification createNotification() {
return new SmsNotification();
}
}
Client code works purely against NotificationSender. It never imports EmailNotification or SmsNotification:
NotificationSender sender = new EmailSender(); // or SmsSender — one line change
sender.notify("Your order has shipped.");
When to choose Factory Method: You have a single product hierarchy, and you expect new variants to arrive over time. Each variant gets its own concrete creator class. Adding a PushNotification later means adding two classes — no existing code changes.
Abstract Factory
Abstract Factory goes one level further: it groups families of related products behind a single factory interface. Every concrete factory produces a full set of products that are guaranteed to be compatible with each other.
Consider a UI toolkit that must render on both a light theme and a dark theme. Each theme needs a matching Button and Checkbox. Mixing a dark Button with a light Checkbox would look broken — the factory prevents that mistake:
// Product interfaces
public interface Button { void render(); }
public interface Checkbox { void render(); }
// Light-theme products
public class LightButton implements Button {
@Override public void render() { System.out.println("[ Light Button ]"); }
}
public class LightCheckbox implements Checkbox {
@Override public void render() { System.out.println("☐ Light Checkbox"); }
}
// Dark-theme products
public class DarkButton implements Button {
@Override public void render() { System.out.println("[ Dark Button ]"); }
}
public class DarkCheckbox implements Checkbox {
@Override public void render() { System.out.println("■ Dark Checkbox"); }
}
// Abstract Factory — one method per product type
public interface ThemeFactory {
Button createButton();
Checkbox createCheckbox();
}
// Concrete factories — one per family
public class LightThemeFactory implements ThemeFactory {
@Override public Button createButton() { return new LightButton(); }
@Override public Checkbox createCheckbox() { return new LightCheckbox(); }
}
public class DarkThemeFactory implements ThemeFactory {
@Override public Button createButton() { return new DarkButton(); }
@Override public Checkbox createCheckbox() { return new DarkCheckbox(); }
}
The application class receives the factory via its constructor — classic dependency injection:
public class Application {
private final Button button;
private final Checkbox checkbox;
public Application(ThemeFactory factory) {
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
public void renderUI() {
button.render();
checkbox.render();
}
}
// Wiring — only this line decides the theme
ThemeFactory factory = new DarkThemeFactory();
Application app = new Application(factory);
app.renderUI();
Reading theme from config: In a real app you would read a property (e.g., theme=dark) from application.properties or an environment variable and instantiate the correct factory once in your bootstrap / DI container. Every downstream class stays unaware of the theme choice.
Comparing the Two Patterns
- Factory Method — single product, variation via subclassing the creator. Fine for moderate numbers of variants.
- Abstract Factory — multiple related products that must stay consistent; variation via swapping the whole factory object. Better for platform/theme/environment families.
- Abstract Factory often uses Factory Methods internally (each
create* method is effectively a factory method).
Static Factory Methods — a Lightweight Cousin
Java's own API uses static factory methods (e.g., List.of(), Optional.of(), Path.of()) as a simpler alternative when subclassing is unnecessary. They are not the GoF Factory Method pattern but share the intent of hiding construction:
public final class Money {
private final long cents;
private final String currency;
private Money(long cents, String currency) { // private constructor
this.cents = cents;
this.currency = currency;
}
public static Money of(long cents, String currency) {
if (cents < 0) throw new IllegalArgumentException("Negative money");
return new Money(cents, currency);
}
public static Money ofDollars(double amount) {
return new Money(Math.round(amount * 100), "USD");
}
}
// Callers never write new Money(...)
Money price = Money.ofDollars(9.99);
Common pitfall: Overusing factories for every single class adds unnecessary indirection. Apply the pattern when you genuinely expect multiple implementations or need to decouple the creation site from the construction details. If there is only ever one concrete class and that will not change, a direct new is simpler and clearer.
Trade-offs and Professional Notes
- Factory patterns shine in framework and library design where the library cannot know which concrete class the consumer will provide.
- In Spring applications, the IoC container is itself an Abstract Factory —
@Bean methods are factory methods that Spring calls to build the application context.
- Abstract Factory increases the number of interfaces and classes. Keep family boundaries small and well-named to avoid confusion.
- Favour interface-based factory parameters (
ThemeFactory factory) over concrete types — this keeps the constructor mockable in unit tests.
Summary
Factory Method decouples a creator from its concrete product by delegating instantiation to a subclass. Abstract Factory extends this to create families of related objects through a shared interface, ensuring all products within a family are compatible. Both patterns reduce coupling at the construction site and make your code open for new variants without modifying existing logic.