Design patternCreational Patterns
Factory Method
Let a subclass or a supplied function decide which concrete object to create, so shared workflow code depends only on an interface.
- Complexity
- Low
- Seen in the wild
- High
- Also known as
- Virtual Constructor
Intent
Declare a method that returns an object of some interface type, and let subclasses (or a passed-in function) override it to choose the concrete class. The surrounding workflow is written once against the interface and never names the concrete type it works with.
Forces
- Shared workflow vs varying collaborator: the steps around an object stay the same, but which object is used differs per variant.
- Calling
newdirectly vs depending on abstractions: a hard-coded constructor ties reusable code to one implementation. - Adding a variant vs editing working code: a new kind of product should not mean another branch in the shared method.
- Testability vs construction buried inside logic: you can't swap in a fake when the real object is built halfway through a method.
Therefore
Move the new call into an overridable creation method, and write the rest of the workflow against the interface it returns.
The base class (the creator) owns the algorithm and calls createX() when it needs a product. Each subclass overrides only that method. In languages without inheritance, or when you prefer composition, the factory method becomes a function field or constructor parameter: same idea, fewer classes.
Structure
Diagram source (Mermaid)
classDiagram
class ContactImport {
<<abstract>>
+run(raw) ImportSummary
+createParser() RecordParser
}
class CsvImport {
+createParser() RecordParser
}
class AddressBookImport {
+createParser() RecordParser
}
class RecordParser {
<<interface>>
+parse(raw) Contact[]
}
class CsvParser {
+parse(raw) Contact[]
}
class BlockParser {
+parse(raw) Contact[]
}
ContactImport <|-- CsvImport
ContactImport <|-- AddressBookImport
ContactImport ..> RecordParser : uses
RecordParser <|.. CsvParser
RecordParser <|.. BlockParser
CsvImport ..> CsvParser : creates
AddressBookImport ..> BlockParser : createsParticipants
- Product (
RecordParser): the interface the workflow depends on. - Concrete products (
CsvParser,BlockParser): the implementations that actually get built. - Creator (
ContactImport): runs the shared workflow and declares the factory methodcreateParser(). - Concrete creators (
CsvImport,AddressBookImport): override the factory method to return one concrete product.
Use it when
- A base class or framework runs a fixed workflow but must let extensions decide which helper object it uses.
- You find the same method duplicated across classes except for the one
new SomethingSpecific()line. - A library needs users to plug in their own implementation of an internal component without forking it.
- You need a seam for tests: overriding the creation method is the smallest change that lets a test inject a fake.
Do not use it when
- There is one concrete product and no realistic second. Call the constructor; add the seam when the second variant shows up.
- The only reason for subclassing is to pick the product. Pass the product - or a factory function such as
() => RecordParser- into the constructor instead; in a language with first-class functions this is plain dependency injection and avoids a class per variant. Keep the overridable method when subclasses must weave creation into their own setup logic. - You need to create several related objects that must match each other. Use Abstract Factory.
- The selection is a lookup from a config string to a class. A map of key to constructor is clearer than a creator hierarchy.
Application grill
Inspect first: the runtime choices between concrete classes and who makes them (callers, config, environment); existing createX() helpers - many codebases already have this pattern under a plain name; a DI container's presence.
Missing pieces: none beyond the language: pass a creator function. A DI container helps only when construction graphs are wide and wired in many entry points; do not introduce one for a single product choice.
Ask before you build:
- Scale: how many products exist and how often does the set change - a stable pair is a conditional, not a factory?
- Performance: is creation on a per-request path where the indirection shows?
- Reliability: must an unknown product fail at startup (validate the registry) or at first use?
- Budget: the indirection's review cost versus the branch it removes - does the set actually change?
- Implementation: where does the product registry live - code, configuration, or plugins - and who extends it?
Implementation steps
- Find the place where shared code calls a concrete constructor, and extract an interface for what the code actually uses from that object.
- Make the shared code depend on the interface type only.
- Replace the
newcall with a call to a creation method (createParser()). Start with a default implementation that returns the existing class, and keep tests green. - Make the creation method abstract (or keep the default) and move each variant into a subclass that overrides it.
- If subclasses exist only to override that one method, switch to a constructor parameter holding a factory function and delete the subclasses.
- Keep any parameters needed for construction (
delimiter) on the concrete creator, not on the shared workflow.
Example
A CRM imports contacts from CSV exports and from address-book exports. The import workflow (parse, validate, count) is the same; only the parser differs.
interface Contact {
email: string;
name: string;
}
interface RecordParser {
parse(raw: string): Contact[];
}
class CsvParser implements RecordParser {
constructor(private readonly delimiter: string) {}
parse(raw: string): Contact[] {
const [header, ...rows] = raw.trim().split("\n");
const cols = header.split(this.delimiter);
return rows.map((row) => {
const f = row.split(this.delimiter);
return { email: f[cols.indexOf("email")] ?? "", name: f[cols.indexOf("name")] ?? "" };
});
}
}
// Address-book export: "key: value" lines, a blank line between contacts.
class BlockParser implements RecordParser {
parse(raw: string): Contact[] {
return raw.trim().split("\n\n").map((block) => {
const fields = new Map(block.split("\n").map((l) => l.split(": ", 2) as [string, string]));
return { email: fields.get("email") ?? "", name: fields.get("name") ?? "" };
});
}
}
abstract class ContactImport {
// The factory method: subclasses decide which parser to build.
protected abstract createParser(): RecordParser;
run(raw: string): { imported: number; rejected: number } {
const contacts = this.createParser().parse(raw);
const valid = contacts.filter((c) => c.email.includes("@"));
return { imported: valid.length, rejected: contacts.length - valid.length };
}
}
class CsvImport extends ContactImport {
constructor(private readonly delimiter: string) {
super();
}
protected createParser(): RecordParser {
return new CsvParser(this.delimiter);
}
}
class AddressBookImport extends ContactImport {
protected createParser(): RecordParser {
return new BlockParser();
}
}
console.log(new CsvImport(";").run("email;name\nana@example.com;Ana\nnot-an-email;Bo"));
// { imported: 1, rejected: 1 }
const book = "email: li@example.com\nname: Li\n\nemail: kim@example.com\nname: Kim";
console.log(new AddressBookImport().run(book)); // { imported: 2, rejected: 0 }Trade-offs
Gains
- The shared workflow depends on an interface, not on any concrete class.
- A new input format is a new creator and product;
run()is untouched. - Construction is a single, overridable seam that tests can replace.
Costs
- One creator subclass per product is a parallel hierarchy that grows with every variant.
- Inheritance couples the variant to the base class's internals. A constructor parameter avoids that.
- Readers must look in the subclass to learn what the workflow actually uses.
Relations
- Abstract Factory is often a set of factory methods grouped on one object, producing a matched family instead of one product.
- Template Method is the larger shape a factory method usually lives in: a fixed algorithm with an overridable step, where the step is "create the collaborator".
- Prototype creates by copying a configured instance, so it needs no creator subclass per product.
- Builder concerns how one complex object is assembled step by step. Factory Method only decides which class to instantiate.
- Strategy is what you often get after refactoring: pass the parser in, and the creator hierarchy disappears.
Agent checklist
- There are at least two concrete products today, or the code is a library extension point. Otherwise keep the constructor call.
- You considered a constructor parameter (an object or a factory function) before adding subclasses.
- The shared workflow references only the product interface, with no
instanceofchecks on the result. - Construction parameters live on the concrete creator, not on the shared workflow.
- Existing tests pass unchanged, and each concrete creator has a test that checks which product it returns.
See also
- Abstract FactoryCreate families of related objects through one interface so client code always gets a matching set and never names the concrete classes.
- Template MethodFix an algorithm's sequence of steps in a base class and let subclasses fill in or override individual steps without changing the order.
- PrototypeCreate new objects by copying a configured existing instance, so callers get a ready-made variant without knowing its class or setup.
- BuilderAssemble a complex object through named, step-by-step calls and validate it once in build(), instead of a constructor with many parameters.
- StrategyPut each interchangeable algorithm behind one interface so the caller picks a behavior at runtime without branching on it.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.