skip to content

When should you deviate from the GRASP "Creator" heuristic and introduce a dedicated factory (Factory Method, Abstract Factory, Builder, or a Pure Fabrication factory class) instead of letting the aggregating/using class call the constructor directly?

level: seniorimportance: should knowfreq 42%

answer

  1. Creator = default for simple new
  2. variability → Factory Method / registry
  3. families → Abstract Factory; many params → Builder
  4. infra needed → pass value in, or app-layer factory
  5. factory changes HOW, not WHO owns

basics

~20 s

Deviate when construction is complicated or must vary: choosing among subclasses, reading configuration, caching or pooling, many optional parameters, or needing infrastructure like a clock or database. Then a factory keeps the owning class simple and lets the built type change.

solid answer

~60 s

Creator is a default for *simple* instantiation; the escape hatch is triggered by four forces. (1) **Complexity** — multi-step assembly, validation, many optional parameters: use a **Builder**. (2) **Variability of type** — the concrete class depends on runtime data, configuration, or plugin registration: use **Factory Method** or a lookup/registry factory, so the caller depends only on an abstraction. (3) **Family consistency** — several related products must come from the same variant: use **Abstract Factory**. (4) **Unwanted dependencies** — construction needs a clock, id generator, repository, or remote service that a domain object must not import; or construction is cached/pooled/recycled. In all cases the factory is typically a **Pure Fabrication** — an invented class with no domain counterpart — introduced precisely because following Creator would damage **High Cohesion** or **Low Coupling**, which outrank Creator. Note the ownership subtlety: delegating construction does not move ownership. The aggregate still calls the factory and still holds the result, so invariants stay at the owner; the factory only encapsulates *how* the instance is produced, not *who* is responsible for it.

go deeper

for a junior

Say Creator works for simple creation; when construction is complicated or the exact class depends on runtime data, use a factory.

for a middle

Name the specific patterns per force: Builder for many parameters, Factory Method/registry for varying type, Abstract Factory for families, and note infrastructure dependencies as a trigger.

for a senior

Add the cohesion/coupling veto, Pure Fabrication framing, the ownership-vs-production distinction, and explicit anti-triggers (speculative generality).

for a principal

Discuss creation policy at architecture scale: composition root vs domain creation, injected factories bridging container-managed and per-operation objects, Protected Variations as the criterion for which construction sites are worth abstracting, and the maintenance cost of gratuitous indirection.

## Recap of the baseline **GRASP Creator**: assign instantiation of `A` to a class `B` that aggregates, contains, records, closely uses `A`, or holds `A`'s initializing data — because `B` already depends on `A`, so no new coupling is introduced. Larman states this explicitly as a heuristic applicable when creation is straightforward, with factories as the recognised alternative. ## The forces that justify overriding it ### Force 1 — Construction is complex or has many optional parts Symptoms: a constructor with 8+ parameters, telescoping overloads, order-dependent setup steps, cross-field validation. **Answer: Builder.** A step-wise object that accumulates configuration and validates once at `build()`. The owner still calls it; the mess is quarantined. ### Force 2 — The concrete type varies at runtime Symptoms: `if (kind == "card") new CardPayment() else if ...`, plugin/extension points, feature flags, per-tenant behaviour, choosing an implementation by locale or jurisdiction. Why Creator fails here: a hard-coded `new` names a **concrete** class. That is not substitutable, so **Protected Variations** (the GRASP principle of shielding elements from variation behind a stable interface) is violated. **Answers:** - **Factory Method** — a method (often overridden in subclasses) that returns an abstraction; the caller stays ignorant of the concrete type. - **Registry / parameterised factory** — a map from a discriminator to a supplier, so new variants register instead of editing a conditional (Open/Closed). - **Abstract Factory** — when a *family* of related products must all come from the same variant and mixing variants would be invalid (e.g. a whole rendering backend, a whole persistence dialect). ### Force 3 — Construction needs collaborators the owner must not have A domain entity should not import a database repository, an HTTP client, a clock, a random source, or a configuration reader. If constructing the part requires those, following Creator would drag infrastructure into the domain — a coupling and testability disaster. **Answers, in order of preference:** 1. **Resolve the value outside and pass it in** (`now`, `newId`, `taxRate`) — cheapest, keeps the entity pure and deterministic. 2. **A factory in the application layer** holding those collaborators, which the entity or service calls. 3. Only last: inject the service into the entity (usually a smell). ### Force 4 — Instances are not simply "new" Caching, interning, object pooling, flyweights, singletons/scoped instances, reuse of immutable values, or creation that must be observed/registered/audited. A raw constructor cannot express any of these; a static factory method or factory object can. ### Force 5 — Deserialization/mapping boundaries Building objects from JSON/rows/messages involves parsing, defaulting, and version tolerance. That logic is not domain behaviour; it belongs to a mapper/assembler (again a Pure Fabrication), not to the aggregate. ## What does NOT justify a factory - "We might need to swap it one day" with no actual variant — speculative generality; a factory with a single implementation is pure indirection cost (harder navigation, an extra hop in stack traces, a bigger public surface). - "Everything should go through factories for consistency" — dogma; it makes trivial value objects ceremonious. - "To make it testable" when the object is a simple value with no side effects — you can just construct it in the test. ## The ownership subtlety (the point most candidates miss) Delegating construction to a factory **does not transfer responsibility for the object**. Creator answers *who is responsible for the instance existing*; the factory answers *how the instance is produced*. Best practice keeps both facts visible: ``` class Order { addLine(product, qty) { line = lineFactory.create(this, product, qty) // HOW is delegated lines.add(line) // WHO owns it is unchanged recalculateTotal() } } ``` If instead the application service calls the factory and pushes the result into an exposed collection, you have lost the invariant boundary — the factory did not fix that, it just moved the `new`. ## Interaction with dependency injection DI containers wire **long-lived collaborators** (services, gateways, repositories) at a **composition root** near the entry point. They are a poor fit for short-lived, data-carrying objects (entities, value objects, events, DTOs), which are created per operation from runtime data — those are Creator's territory. When a short-lived object genuinely needs an injected collaborator to be built, the idiomatic bridge is an **injected factory/provider**: the container supplies the factory, the domain calls it with runtime arguments. ## Cost checklist before adding a factory | Cost | Detail | |---|---| | Indirection | one more hop when reading code; "go to implementation" no longer lands on the type | | Surface area | a new public type to name, document, test, and version | | Lifetime confusion | who owns the produced object becomes less obvious | | Over-abstraction | interface + single impl invites premature generality | Add the factory when a real force above is present; otherwise let Creator stand.

  • Does introducing a factory mean the aggregate no longer 'owns' the created object?
    No. The factory encapsulates how the instance is produced; the aggregate still calls it, stores the result, and enforces invariants. Ownership only moves if you also move the storage and the rules out of the aggregate — which is a separate, usually undesirable, decision.
  • How does Protected Variations relate to the choice between Creator and Factory?
    Protected Variations says to shield elements from variation behind a stable interface. A hard-coded 'new' of a concrete type is unprotected. When the concrete type is a predicted point of variation, replace direct construction with a factory returning an abstraction; when there is no real variation, the protection is speculative cost.
  • When is a static factory method on the created class enough, instead of a separate factory class?
    When the variation is limited to validation, naming clarity, caching/interning, or choosing among the type's own known subtypes, and no external collaborators are required. Once construction needs injected dependencies or must be substituted in tests, a separate factory object is warranted.

A homeowner (the owner) still owns the kitchen, but hires a contractor (factory) when the job needs specialist tools and permits. Hiring the contractor doesn't transfer ownership of the house — it only outsources the build step.

context