Design Patterns, Explained Simply · Creational Patterns: Creating Objects Flexibly
Factory Method
Let subclasses decide which concrete object to create, so the code that uses the object never names its class.
The problem
A reporting feature exports rows as CSV. Then customers want JSON. Then XML. The export() method has if format == "csv" checks in three places: building the header, writing each row, and joining the output. Each new format means finding and editing every one of those checks.
The real issue is that export() both decides which formatter to use and uses it. Those two jobs change for different reasons.
Real-world analogy
A coffee chain has one standard way to serve a drink: take the order, make the drink, hand it over. Which machine makes the drink is up to each branch: an espresso bar uses an espresso machine, a kiosk uses a capsule machine. Head office writes the serving process once; each branch decides what to "create".
Without the pattern
def export(rows, fmt):
lines = []
for row in rows:
if fmt == "csv":
lines.append(f"{row['id']},{row['name']}")
elif fmt == "json":
lines.append(f'{{"id": {row["id"]}, "name": "{row["name"]}"}}')
# every new format: another branch here, and in any other place that checks fmt
return "\n".join(lines)
The pattern
- Creator (
Report): has the shared algorithm (export()) and declares a factory method (create_formatter()) that returns a product. - Concrete creators (
CsvReport,JsonReport): override the factory method to return a specific product. - Product (
Formatter): the interface the creator uses (format(row)). - Concrete products (
CsvFormatter,JsonFormatter).
The creator only talks to the product interface, so adding XML means adding XmlReport and XmlFormatter. export() doesn't change.
The code
class CsvFormatter:
def format(self, row):
return f"{row['id']},{row['name']}"
class JsonFormatter:
def format(self, row):
return f'{{"id": {row["id"]}, "name": "{row["name"]}"}}'
class Report:
def __init__(self, rows):
self.rows = rows
def create_formatter(self): # the factory method
raise NotImplementedError
def export(self): # shared algorithm, never names a concrete formatter
formatter = self.create_formatter()
return "\n".join(formatter.format(row) for row in self.rows)
class CsvReport(Report):
def create_formatter(self):
return CsvFormatter()
class JsonReport(Report):
def create_formatter(self):
return JsonFormatter()
rows = [{"id": 1, "name": "Ada"}, {"id": 2, "name": "Linus"}]
for report in [CsvReport(rows), JsonReport(rows)]:
print(f"-- {type(report).__name__}")
print(report.export())
Output
-- CsvReport
1,Ada
2,Linus
-- JsonReport
{"id": 1, "name": "Ada"}
{"id": 2, "name": "Linus"}type Row = { id: number; name: string };
interface Formatter {
format(row: Row): string;
}
class CsvFormatter implements Formatter {
format(row: Row) { return `${row.id},${row.name}`; }
}
class JsonFormatter implements Formatter {
format(row: Row) { return `{"id": ${row.id}, "name": "${row.name}"}`; }
}
abstract class Report {
constructor(protected rows: Row[]) {}
protected abstract createFormatter(): Formatter; // the factory method
export(): string { // shared algorithm, never names a concrete formatter
const formatter = this.createFormatter();
return this.rows.map((row) => formatter.format(row)).join("\n");
}
}
class CsvReport extends Report {
protected createFormatter() { return new CsvFormatter(); }
}
class JsonReport extends Report {
protected createFormatter() { return new JsonFormatter(); }
}
const rows: Row[] = [{ id: 1, name: "Ada" }, { id: 2, name: "Linus" }];
for (const report of [new CsvReport(rows), new JsonReport(rows)]) {
console.log(`-- ${report.constructor.name}`);
console.log(report.export());
}
Output
-- CsvReport
1,Ada
2,Linus
-- JsonReport
{"id": 1, "name": "Ada"}
{"id": 2, "name": "Linus"}import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Row> rows = List.of(new Row(1, "Ada"), new Row(2, "Linus"));
for (Report report : List.of(new CsvReport(rows), new JsonReport(rows))) {
System.out.println("-- " + report.getClass().getSimpleName());
System.out.println(report.export());
}
}
}
record Row(int id, String name) {}
interface Formatter {
String format(Row row);
}
class CsvFormatter implements Formatter {
public String format(Row row) { return row.id() + "," + row.name(); }
}
class JsonFormatter implements Formatter {
public String format(Row row) { return "{\"id\": " + row.id() + ", \"name\": \"" + row.name() + "\"}"; }
}
abstract class Report {
protected final List<Row> rows;
Report(List<Row> rows) { this.rows = rows; }
protected abstract Formatter createFormatter(); // the factory method
String export() { // shared algorithm, never names a concrete formatter
Formatter formatter = createFormatter();
return rows.stream().map(formatter::format).collect(Collectors.joining("\n"));
}
}
class CsvReport extends Report {
CsvReport(List<Row> rows) { super(rows); }
protected Formatter createFormatter() { return new CsvFormatter(); }
}
class JsonReport extends Report {
JsonReport(List<Row> rows) { super(rows); }
protected Formatter createFormatter() { return new JsonFormatter(); }
}
Output
-- CsvReport
1,Ada
2,Linus
-- JsonReport
{"id": 1, "name": "Ada"}
{"id": 2, "name": "Linus"}Run this on your computer — see “Get Started” for setup.
<?php
interface Formatter {
public function format(array $row): string;
}
class CsvFormatter implements Formatter {
public function format(array $row): string { return "{$row['id']},{$row['name']}"; }
}
class JsonFormatter implements Formatter {
public function format(array $row): string { return "{\"id\": {$row['id']}, \"name\": \"{$row['name']}\"}"; }
}
abstract class Report {
public function __construct(protected array $rows) {}
abstract protected function createFormatter(): Formatter; // the factory method
public function export(): string { // shared algorithm, never names a concrete formatter
$formatter = $this->createFormatter();
return implode("\n", array_map(fn($row) => $formatter->format($row), $this->rows));
}
}
class CsvReport extends Report {
protected function createFormatter(): Formatter { return new CsvFormatter(); }
}
class JsonReport extends Report {
protected function createFormatter(): Formatter { return new JsonFormatter(); }
}
$rows = [["id" => 1, "name" => "Ada"], ["id" => 2, "name" => "Linus"]];
foreach ([new CsvReport($rows), new JsonReport($rows)] as $report) {
echo "-- ", get_class($report), "\n";
echo $report->export(), "\n";
}
Output
-- CsvReport
1,Ada
2,Linus
-- JsonReport
{"id": 1, "name": "Ada"}
{"id": 2, "name": "Linus"}A simpler cousin you will see more often is the simple factory: one function like create_formatter("csv") that returns the right object. It isn't one of the 23 GoF patterns, but it solves the same "who decides which class" problem without subclasses.
When to use it
- The shared workflow is fixed, but which object it works with varies by subclass, configuration or platform.
- You want to add new product types without editing the code that uses them (Open/Closed).
- A framework needs to let users plug in their own objects (a base class with an overridable
create_*hook).
When not to use it
- There is only one product type and no sign of a second. A plain
newis clearer. - A simple factory function or a dictionary from name to class is enough. You don't need a subclass per product.
- In languages with first-class functions, passing the formatter (or a function that makes one) to
export()is often simpler than subclassing.
You've already used it
- Java:
Collection.iterator()is a factory method. Each collection returns its own iterator type. - Python:
logging.getLogger()andopen(), which picks a text or binary file object based on the mode. - Django:
get_form_class()in generic views andget_queryset()are overridable factory methods. - Laravel: a model's
newCollection()andnewEloquentBuilder()let subclasses supply custom objects.
Key points
- A factory method is an overridable method that creates the object a shared algorithm needs.
- The algorithm talks only to the product interface, so new products need no edits to it.
- The simple factory (one creating function) is the everyday version; Factory Method adds subclass control.
- Further reading: Refactoring Guru: Factory Method.
Practice questions
What problem does the Factory Method pattern solve?
Answer
It separates deciding which object to create from using it. The base class has a shared algorithm that calls an overridable factory method; subclasses return different concrete products. New products can be added without editing the algorithm.
What is the difference between a simple factory and the Factory Method pattern?
Answer
A simple factory is one function or class with a conditional that returns the right object (create_formatter('csv')). Factory Method uses inheritance: each subclass overrides the creating method. The simple factory is more common; Factory Method suits frameworks where users extend a base class.
Give an example of Factory Method in a standard library.
Answer
Java's Collection.iterator(): every collection class returns its own Iterator implementation, and code that loops over collections only depends on the Iterator interface.
Open in the app to mark lessons complete, take notes and track your progress.