LESSON 4.3
Factory Method
Define an interface for creating an object, but let subclasses or specialized creators decide which class to instantiate.
12 min read
Real-world analogy
A national postal and parcel logistics network operates delivery hubs across various terrains. Every hub executes the exact same core pipeline: scan package, verify weight, generate tracking manifest, and dispatch shipment. However, a coastal maritime hub creates cargo boat shipments, an airport hub creates air cargo flights, and a city center hub creates electric delivery vans. The core dispatch procedure is identical; only the vehicle object created for transport differs.
The problem
Consider an automated logistics and document notification system. In the initial release, the application only formats and transmits PDF invoices. Callers directly instantiate PDFDocument inside their core business workflows. When clients later demand HTML email invoices and plain-text receipts, developers add conditional branches directly inside the business service.
The naive code below demonstrates how object creation becomes tightly coupled to domain operations:
class InvoiceService {
generateInvoice(type, orderData) {
let document;
// Hard-wired conditionals: every new document type breaks this method!
if (type === "pdf") {
document = new PDFDocument();
} else if (type === "html") {
document = new HTMLDocument();
} else {
throw new Error(`Unsupported type: ${type}`);
}
const payload = document.format(orderData);
console.log(`Auditing invoice generation for order: ${orderData.id}`);
return payload;
}
}
class PDFDocument {
format(data) { return `<PDF>Invoice #${data.id}: $${data.total}</PDF>`; }
}
class HTMLDocument {
format(data) { return `<div>Invoice #${data.id}: $${data.total}</div>`; }
}
const service = new InvoiceService();
console.log(service.generateInvoice("pdf", { id: 101, total: 250 }));public class Example {
static class PDFDocument {
public String format(int id, double total) {
return "<PDF>Invoice #" + id + ": $" + total + "</PDF>";
}
}
static class HTMLDocument {
public String format(int id, double total) {
Every time a new format is introduced (Markdown, XML, EDI), InvoiceService must be reopened and modified. Business logic and creation mechanics are tangled together, preventing callers from extending document generation independently.
The solution
Intent: Define an interface for creating an object, but let subclasses or specialized factory implementations decide which class to instantiate.
The participants are:
- Product: Declares the common interface for all objects created by the factory method.
- ConcreteProduct: Implements the Product interface with specific behavior.
- Creator: Declares the factory method returning a Product. May also implement a default template method that relies on the product.
- ConcreteCreator: Overrides the factory method to return an instance of a specific ConcreteProduct.
Step-by-step implementation
Step 1: Define the Product interface
Extract the common operations shared by all product variants into a unified contract.
Step 2: Declare the Creator with the factory method
Define an abstract base creator class whose business workflow relies on the factory method without knowing which concrete product is instantiated.
Step 3: Implement concrete creators
Each concrete creator subclass overrides the factory method to instantiate its specific product.
class PDFDocumentCreator extends DocumentCreator {
createDocument() {
return new PDFDocument();
}
}
class HTMLDocumentCreator extends DocumentCreator {
createDocument() {
return new HTMLDocument();
}
}The complete example
Here is a runnable document publishing engine. Notice how the client orchestrates invoicing through abstract creators, allowing new formats to be added without touching the engine.
PUBLISHED -> [PDF] Invoice #401 billed for $199.99
PUBLISHED -> <html><body>Invoice #402: $49.5</body></html>Variations
1. Simple Factory (Static Factory)
A standalone helper class containing a static method with a switch or dictionary lookup:
public class DocumentFactory {
public static Document create(String type) {
if ("pdf".equalsIgnoreCase(type)) return new PDFDocument();
if ("html".equalsIgnoreCase(type)) return new HTMLDocument();
throw new IllegalArgumentException("Unknown type: " + type);
}
}- Difference: Not a classic GoF pattern because it does not use subclassing, but very common in production code when subclassing creators is unnecessary overhead.
2. Registry-based Factory (Plugin pattern)
Instead of a hardcoded switch, creators register themselves into a lookup map dynamically:
class DocumentRegistry {
static #creators = new Map();
static register(type, creatorFn) {
this.#creators.set(type.toLowerCase(), creatorFn);
}
static create(type) {
const creator = this.#creators.get(type.toLowerCase());
- Pros: Completely satisfies the Open/Closed Principle: plugins can register new product types at application startup without editing the factory class.
3. Parameterized Factory Method
The creator's factory method takes an argument indicating which product variant to instantiate:
public abstract class DocumentPublisher {
public abstract Document createDocument(String levelOfDetail);
}Where you've already used it
- Java Standard Library (JDK):
java.util.Calendar.getInstance(): Returns aGregorianCalendarorJapaneseImperialCalendarbased on JVM locale.java.text.NumberFormat.getInstance(): Returns a locale-sensitive currency or decimal formatter.java.nio.charset.Charset.forName(String): Returns the appropriate concrete encoding implementation.
- Logging frameworks (SLF4J, Log4j):
org.slf4j.LoggerFactory.getLogger(Class): Creates a configuredLoggerbinding depending on classpath adapters (Logback, Log4j2).
- Browser and DOM APIs:
document.createElement("div"): Factory method creating concrete DOM element subclasses (HTMLDivElement,HTMLInputElement) behind theHTMLElementinterface.
- React and modern frontend:
React.createElement(type, props): Instantiates virtual DOM element descriptors based on component types or intrinsic HTML tags.
Pros and cons
| Strengths | Weaknesses |
|---|---|
| Adheres strictly to the Open/Closed Principle | Can introduce class proliferation (each product needs a creator subclass) |
| Decouples business logic from concrete product classes | Adds indirection when simple constructor calls would suffice |
| Promotes Single Responsibility Principle (creation isolated) | Requires subclasses even when behavior difference is trivial |
| Simplifies testing via mock creator subclasses | Can complicate code navigation in large codebases |
When not to use it
If your application only has one concrete implementation of a product that is unlikely to change, do not introduce Factory Method. Direct instantiation is simpler and clearer. Adopt Factory Method when products vary across dimensions (environments, formats, platforms) or when framework callers need to configure which product is created.
Related patterns
| Pattern | Relationship to Factory Method |
|---|---|
| Abstract Factory | Abstract Factory often implements its creation methods using Factory Methods, coordinating families of related products. |
| Template Method | Factory Method is frequently called from inside a Template Method's algorithm loop. |
| Prototype | Prototype uses object cloning to create instances, avoiding the creator inheritance hierarchy required by Factory Method. |
| Builder | Factory Method creates products in a single step; Builder constructs complex products incrementally across multiple steps. |
Factory Method: notification channels
Refactor a monolithic notification dispatcher with hardcoded switch branches into the Factory Method pattern, enabling new communication channels to be added without modifying the core dispatcher.
Click Run tests to evaluate your solution against the behavior and structural test suite.
Interview questions
Q1: What is the main structural difference between Simple Factory and Factory Method?
Simple Factory uses a single class with a conditional branch to instantiate products, meaning adding a new product requires modifying the factory class. Factory Method uses polymorphism and subclassing, allowing new products to be added via new creator subclasses without modifying existing code.
Q2: How does Factory Method relate to the Dependency Inversion Principle (DIP)?
High-level classes depend on the abstract Product interface rather than concrete product classes. The creator encapsulates the decision of which concrete class to instantiate, preventing high-level logic from directly coupling to low-level implementation details.
Q3: Can a Factory Method provide a default implementation?
Yes. In the GoF specification, the creator class can provide a default implementation of the factory method returning a standard product, allowing subclasses to override it only when they need a non-default product.
Q4: When should you prefer a Registry Factory over GoF subclassing?
Prefer a Registry Factory when product types are dynamic or loaded via plugins at runtime. Instead of creating a parallel class hierarchy of creators, products register a supplier lambda into a map, avoiding inheritance overhead.
Try it
Logging Framework LLD
Compose structured logging, redaction and bounded asynchronous output.