# Factory Method

> Let a subclass or a supplied function decide which concrete object to create, so shared workflow code depends only on an interface.

- kind: pattern
- category: creational
- aliases: Virtual Constructor
- canonical: https://aipatterns.dev/design-patterns/factory-method
- inspired-by: https://refactoring.guru/design-patterns/factory-method

## 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 `new` directly** 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

```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 : creates
```

### Participants

- **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 method `createParser()`.
- **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](https://aipatterns.dev/design-patterns/abstract-factory.md).
- 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

1. Find the place where shared code calls a concrete constructor, and extract an interface for what the code actually uses from that object.
2. Make the shared code depend on the interface type only.
3. Replace the `new` call with a call to a creation method (`createParser()`). Start with a default implementation that returns the existing class, and keep tests green.
4. Make the creation method abstract (or keep the default) and move each variant into a subclass that overrides it.
5. If subclasses exist only to override that one method, switch to a constructor parameter holding a factory function and delete the subclasses.
6. 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.

```ts title="contact-import.ts"
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 }
```

```python title="contact_import.py"
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Protocol

@dataclass(frozen=True)
class Contact:
    email: str
    name: str

class RecordParser(Protocol):
    def parse(self, raw: str) -> list[Contact]: ...

class CsvParser:
    def __init__(self, delimiter: str) -> None:
        self.delimiter = delimiter

    def parse(self, raw: str) -> list[Contact]:
        header, *rows = raw.strip().splitlines()
        cols = header.split(self.delimiter)
        records = [dict(zip(cols, row.split(self.delimiter))) for row in rows]
        return [Contact(r.get("email", ""), r.get("name", "")) for r in records]

class BlockParser:
    """Address-book export: 'key: value' lines, a blank line between contacts."""

    def parse(self, raw: str) -> list[Contact]:
        contacts = []
        for block in raw.strip().split("\n\n"):
            fields = dict(line.split(": ", 1) for line in block.splitlines())
            contacts.append(Contact(fields.get("email", ""), fields.get("name", "")))
        return contacts

class ContactImport(ABC):
    @abstractmethod
    def create_parser(self) -> RecordParser:
        """Factory method: each subclass picks its parser."""

    def run(self, raw: str) -> dict[str, int]:
        contacts = self.create_parser().parse(raw)
        valid = [c for c in contacts if "@" in c.email]
        return {"imported": len(valid), "rejected": len(contacts) - len(valid)}

class CsvImport(ContactImport):
    def __init__(self, delimiter: str) -> None:
        self.delimiter = delimiter

    def create_parser(self) -> RecordParser:
        return CsvParser(self.delimiter)

class AddressBookImport(ContactImport):
    def create_parser(self) -> RecordParser:
        return BlockParser()

if __name__ == "__main__":
    print(CsvImport(";").run("email;name\nana@example.com;Ana\nnot-an-email;Bo"))
    # {'imported': 1, 'rejected': 1}
    book = "email: li@example.com\nname: Li\n\nemail: kim@example.com\nname: Kim"
    print(AddressBookImport().run(book))  # {'imported': 2, 'rejected': 0}
```

```go title="contact_import.go"
package main

import (
	"fmt"
	"strings"
)

type Contact struct{ Email, Name string }

type RecordParser interface {
	Parse(raw string) []Contact
}

type CSVParser struct{ Delimiter string }

func (p CSVParser) Parse(raw string) []Contact {
	lines := strings.Split(strings.TrimSpace(raw), "\n")
	col := map[string]int{}
	for i, name := range strings.Split(lines[0], p.Delimiter) {
		col[name] = i
	}
	var out []Contact
	for _, row := range lines[1:] {
		f := strings.Split(row, p.Delimiter)
		out = append(out, Contact{Email: f[col["email"]], Name: f[col["name"]]})
	}
	return out
}

// BlockParser reads "key: value" lines, a blank line between contacts.
type BlockParser struct{}

func (BlockParser) Parse(raw string) []Contact {
	var out []Contact
	for _, block := range strings.Split(strings.TrimSpace(raw), "\n\n") {
		fields := map[string]string{}
		for _, line := range strings.Split(block, "\n") {
			if k, v, ok := strings.Cut(line, ": "); ok {
				fields[k] = v
			}
		}
		out = append(out, Contact{Email: fields["email"], Name: fields["name"]})
	}
	return out
}

// Go has no subclassing, so the factory method is a function field.
type ContactImport struct {
	NewParser func() RecordParser
}

func (ci ContactImport) Run(raw string) (imported, rejected int) {
	for _, c := range ci.NewParser().Parse(raw) {
		if strings.Contains(c.Email, "@") {
			imported++
		} else {
			rejected++
		}
	}
	return imported, rejected
}

func main() {
	csvImport := ContactImport{NewParser: func() RecordParser { return CSVParser{Delimiter: ";"} }}
	fmt.Println(csvImport.Run("email;name\nana@example.com;Ana\nnot-an-email;Bo")) // 1 1

	bookImport := ContactImport{NewParser: func() RecordParser { return BlockParser{} }}
	fmt.Println(bookImport.Run("email: li@example.com\nname: Li\n\nemail: kim@example.com\nname: Kim")) // 2 0
}
```

```java title="ContactImports.java"
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

public class ContactImports {
    record Contact(String email, String name) {}

    interface RecordParser {
        List<Contact> parse(String raw);
    }

    record CsvParser(String delimiter) implements RecordParser {
        public List<Contact> parse(String raw) {
            String[] lines = raw.strip().split("\n");
            List<String> cols = List.of(lines[0].split(Pattern.quote(delimiter)));
            List<Contact> out = new ArrayList<>();
            for (int i = 1; i < lines.length; i++) {
                String[] f = lines[i].split(Pattern.quote(delimiter));
                out.add(new Contact(f[cols.indexOf("email")], f[cols.indexOf("name")]));
            }
            return out;
        }
    }

    // Address-book export: "key: value" lines, a blank line between contacts.
    static final class BlockParser implements RecordParser {
        public List<Contact> parse(String raw) {
            List<Contact> out = new ArrayList<>();
            for (String block : raw.strip().split("\n\n")) {
                Map<String, String> fields = new HashMap<>();
                for (String line : block.split("\n")) {
                    String[] kv = line.split(": ", 2);
                    if (kv.length == 2) fields.put(kv[0], kv[1]);
                }
                out.add(new Contact(fields.getOrDefault("email", ""), fields.getOrDefault("name", "")));
            }
            return out;
        }
    }

    abstract static class ContactImport {
        protected abstract RecordParser createParser(); // the factory method

        String run(String raw) {
            List<Contact> contacts = createParser().parse(raw);
            long valid = contacts.stream().filter(c -> c.email().contains("@")).count();
            return "imported=" + valid + " rejected=" + (contacts.size() - valid);
        }
    }

    static final class CsvImport extends ContactImport {
        private final String delimiter;
        CsvImport(String delimiter) { this.delimiter = delimiter; }
        protected RecordParser createParser() { return new CsvParser(delimiter); }
    }

    static final class AddressBookImport extends ContactImport {
        protected RecordParser createParser() { return new BlockParser(); }
    }

    public static void main(String[] args) {
        System.out.println(new CsvImport(";").run("email;name\nana@example.com;Ana\nnot-an-email;Bo"));
        // imported=1 rejected=1
        String book = "email: li@example.com\nname: Li\n\nemail: kim@example.com\nname: Kim";
        System.out.println(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](https://aipatterns.dev/design-patterns/abstract-factory.md) is often a set of factory methods grouped on one object, producing a matched family instead of one product.
- [Template Method](https://aipatterns.dev/design-patterns/template-method.md) 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](https://aipatterns.dev/design-patterns/prototype.md) creates by copying a configured instance, so it needs no creator subclass per product.
- [Builder](https://aipatterns.dev/design-patterns/builder.md) concerns how one complex object is assembled step by step. Factory Method only decides which class to instantiate.
- [Strategy](https://aipatterns.dev/design-patterns/strategy.md) 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 `instanceof` checks 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.
