Factory Method
Overview
Define an interface for creating an object, but let subclasses decide which class to instantiate.
When to use
- The exact concrete type is decided at runtime.
- You want to decouple creation logic from the workflow that uses the created object.
Java example
interface Exporter {
String export(String data);
}
class JsonExporter implements Exporter {
public String export(String data) {
return "{\"data\":\"" + data + "\"}";
}
}
class CsvExporter implements Exporter {
public String export(String data) {
return "data\n" + data + "\n";
}
}
abstract class DocumentExporter {
public String exportDocument(String data) {
return createExporter().export(data);
}
protected abstract Exporter createExporter();
}
class JsonDocumentExporter extends DocumentExporter {
protected Exporter createExporter() {
return new JsonExporter();
}
}
class CsvDocumentExporter extends DocumentExporter {
protected Exporter createExporter() {
return new CsvExporter();
}
}
TypeScript example
interface Exporter {
export(data: string): string;
}
class JsonExporter implements Exporter {
export(data: string): string {
return JSON.stringify({ data });
}
}
class CsvExporter implements Exporter {
export(data: string): string {
return `data\n${data}\n`;
}
}
abstract class DocumentExporter {
exportDocument(data: string): string {
return this.createExporter().export(data);
}
protected abstract createExporter(): Exporter;
}
class JsonDocumentExporter extends DocumentExporter {
protected createExporter(): Exporter {
return new JsonExporter();
}
}
class CsvDocumentExporter extends DocumentExporter {
protected createExporter(): Exporter {
return new CsvExporter();
}
}
Pros and cons
Pros:
- Centralizes creation logic inside a larger workflow.
- Enables substitution of product types.
Cons:
- Adds extra classes.
- Can be overkill for simple creation.
Common pitfalls
- Using it when a plain constructor or simple function is enough.
- Pushing too much unrelated logic into the creator.