skip to content

The Gang of Four classify Factory Method as a 'class creational' pattern and Abstract Factory, Builder, Prototype and Singleton as 'object creational'. What does that distinction mean, and why does it affect when the concrete class gets decided?

level: seniorimportance: nice to knowfreq 26%

answer

  1. class scope = inheritance = bound when you pick the subclass
  2. object scope = delegation = swappable reference at runtime
  3. only Factory Method is class creational
  4. inheritance multiplies subclasses (creator × product); composition adds
  5. functions/lambdas collapse both in modern languages

basics

~20 s

Class creational means the variation comes from inheritance — a subclass overrides a creation method, so the choice is fixed when you pick the subclass. Object creational means creation is delegated to another object, so you can change it at runtime by swapping that object.

solid answer

~60 s

**Class creational** patterns use **inheritance** to decide what gets created. Factory Method is the only one: the creator class declares an overridable creation step and subclasses supply the concrete product. The binding is made when you instantiate a particular subclass, so it is essentially static per object — changing the product means changing the class of the creator. **Object creational** patterns **delegate** creation to a separate object: an Abstract Factory instance, a Builder, a prototype instance to clone, or a scope/lifetime holder. Because the collaborator is a reference, it can be injected, configured and replaced while the program runs — even per request or per tenant — and one creator can be reused with several strategies. The practical consequences: inheritance binds earlier, produces class explosion when several axes vary independently (creator × product), and consumes the single-inheritance slot in many languages; delegation binds later, composes across axes, and is more testable — which is exactly the 'favor composition over inheritance' guidance. Factory Method is still preferable when a framework wants a subclass-shaped extension point with template logic in the base class.

code

pseudocode · 10 lines
pseudocode
// class creational: product fixed by which subclass you instantiated
abstract class Exporter { abstract fun writer(): Writer
                          fun run(d: Data) { writer().write(d) } }
class CsvExporter : Exporter { override fun writer() = CsvWriter() }

// object creational: product decided by the reference you hold, per call
class Exporter(private val makeWriter: (Format) -> Writer) {
  fun run(d: Data, f: Format) { makeWriter(f).write(d) }
}
Exporter { fmt -> if (fmt == CSV) CsvWriter() else JsonWriter() }

go deeper

for a junior

Say class creational uses inheritance (a subclass picks the product) and object creational delegates to another object you can swap.

for a middle

Add that only Factory Method is class-scoped, that inheritance binds when you choose the subclass while delegation can change at runtime, and give one example of each.

for a senior

Discuss combinatorial explosion across variation axes, the single inheritance slot, encapsulation and testability differences, and when a framework extension point still justifies Factory Method.

for a principal

Connect it to binding time and coupling strategy across the codebase, note that first-class functions collapse most of the distinction, and reason about which extension contracts should be compiler-enforced versus configuration-driven.

## The classification The Gang of Four tag each pattern with a *scope*: - **Class patterns** — the relationship is between classes, established through **inheritance**, and fixed at compile time. - **Object patterns** — the relationship is between **objects**, established through references, and changeable at runtime. Among the five creational patterns, only **Factory Method** is class-scoped. **Abstract Factory, Builder, Prototype and Singleton** are object-scoped. ## Why Factory Method is class-scoped ``` abstract class Importer { abstract fun createParser(): Parser // the variation point fun import(file: File) { // template logic, fixed val rows = createParser().parse(file) persist(rows) } } class CsvImporter : Importer { override fun createParser() = CsvParser() } class JsonImporter : Importer { override fun createParser() = JsonParser() } ``` The product is determined by **which subclass** you instantiated. Once you hold a `CsvImporter`, it will always make a `CsvParser`. To change the product you must construct a different object of a different class. That is inheritance-based variation. ## Why the rest are object-scoped ``` class Importer(private val parsers: ParserFactory) { // a reference, not a subclass fun import(file: File) { persist(parsers.create(file.type).parse(file)) } } ``` The collaborator is a field. Swap it — via constructor injection, configuration, a DI profile, a per-tenant lookup — and the behavior changes without any new class of `Importer`. Builder and Prototype are the same story: you hold *some builder* or *some prototype instance*, and which one you hold is a runtime fact. ## Consequences that matter in practice **Binding time.** Inheritance binds when the object is created; delegation can bind per call. If the choice depends on a request header, a tenant, or a feature flag evaluated mid-flight, delegation is the only comfortable option. **Combinatorial explosion.** With inheritance, two independent variation axes multiply: 3 creator behaviors × 4 product types = 12 subclasses. With delegation you compose 3 + 4 = 7 pieces. This is the same argument that motivates the Bridge and Strategy patterns. **The inheritance slot.** In single-inheritance languages, using the base class for a creation hook consumes the one extension slot the consumer has. Delegation leaves it free. **Encapsulation.** A subclass sees the base class's protected internals — a wide, fragile coupling ('inheritance breaks encapsulation'). A collaborator only sees the interface it was given. **Testability.** Testing a Factory-Method design means subclassing the class under test (a test-only subclass overriding the creation step). Testing a delegating design means passing a fake — smaller, more explicit, no inheritance gymnastics. **Compile-time guarantees.** Inheritance gives you one thing delegation does not: the compiler proves the product type for that subclass, and the base class's template algorithm cannot be bypassed. Frameworks exploit this — 'extend this class and implement `createX()`' is a legible extension contract. ## When Factory Method is still the right choice - A framework defines the workflow and third parties fill in a step (Template Method + Factory Method). - The creation choice belongs conceptually to the subtype: `LinkedList.iterator()` returning a linked-list iterator, `Document` subclasses creating their matching editor. - The consumer and product are genuinely paired, so no independent second axis exists to explode. ## Interactions - Abstract Factory implementations very often use Factory Methods internally — one per product. - In languages with first-class functions, both frequently collapse to passing a **function**: `Importer(parserFor = ::csvParser)` is an object-creational Factory Method with no interface at all. That is the modern default in Kotlin, Python, JavaScript, Go and Rust, and worth naming in an interview. - Prototype is object-creational in the strongest sense: the *instance itself* is the specification, so no class is named at the call site at all — the latest possible binding of the five.

  • Does 'favor composition over inheritance' mean Factory Method should be avoided?
    No. It means do not reach for inheritance when the axes vary independently or the choice must change at runtime. Factory Method remains the clean answer for framework extension points where a subclass must fill a step of a fixed algorithm, where the consumer and product are inherently paired, and where you want the compiler to enforce the contract.
  • How does passing a function instead of a factory interface change the classification?
    It stays object-creational — the function is a value held in a field — but removes the interface and the implementing classes. You get runtime swappability and trivial test substitution (pass a lambda) with almost no ceremony. The trade-off is a less self-documenting contract: a named interface with method names conveys more than a bare `(Format) -> Writer` type, which matters as the number of creation operations grows.
  • Why does inheritance-based creation cause a class explosion where composition does not?
    Because each independent variation axis multiplies rather than adds. If the creator varies 3 ways and the product 4 ways, subclassing requires a class per combination (12); composition needs 3 creators plus 4 products (7) wired at runtime. This is the same force that motivates Bridge — separating an abstraction from its implementation so both can vary independently.

Class creational is a machine whose cutting head is welded on — to cut a different shape you buy a different machine. Object creational is the same machine with a tool chuck: swap the bit mid-job and one machine covers every shape.

saying these in an interview costs you the question

  • Believing 'class creational' means the pattern creates classes, or 'object creational' means it creates objects — both create objects; the words describe how the variation is established
  • Assuming Abstract Factory is inheritance-based because concrete factories inherit an interface — clients delegate to a factory object, which is what sets the scope
  • Claiming delegation is always superior; inheritance gives a compiler-enforced extension contract and keeps template algorithms intact
  • Overlooking that Factory Method consumes the single inheritance slot of the consuming class
  • Failing to note that in languages with first-class functions both often reduce to passing a function

context