Move Creation Knowledge To Factory

Destination
Compare
Symptom
Human

Client code carries the recipe for constructing a complex object — instantiating sub-collaborators, configuring them, wiring them together, applying defaults. Every caller that needs the object repeats the recipe; recipe drift between callers produces subtly different objects.

Agent

Construction recipes duplicated across N caller sites the agent must verify match on every recipe change. Each call site assembles the object with its own subset of defaults; the agent cannot statically guarantee the assemblies stay consistent.

Goal
Human

A Factory class owns the assembly recipe. Callers ask for the finished object by intent (`createOrder(...)`); the recipe lives in one place, evolves in one place, and is tested in one place.

Agent

One factory file the agent reads as the construction contract; callers are short delegations the agent treats opaquely. Per-edit budget for a recipe change collapses to one file.

Before the refactoring

// The caller knows the entire assembly recipe.
function placeOrder(customerId, line1, city, postal, items, jurisdiction) {
const customer = new Customer(customerId);
const address = new Address(line1, city, postal);
customer.setShippingAddress(address);
customer.setBillingAddress(address);
const order = new Order(customer);
for (const item of items) {
const lineItem = new LineItem(item.product, item.quantity);
lineItem.applyDiscount(customer.discountRate);
order.addLineItem(lineItem);
}
order.calculateTotal();
order.applyTax(jurisdiction);
return order;
}

After the refactoring

// The factory owns the recipe; the caller asks for a finished order.
class OrderFactory {
constructor(taxJurisdiction) {
this.taxJurisdiction = taxJurisdiction;
}
createOrder(customerId, line1, city, postal, items) {
const customer = this.buildCustomer(customerId, line1, city, postal);
const order = new Order(customer);
items.forEach((item) => order.addLineItem(this.buildLineItem(item, customer)));
order.calculateTotal();
order.applyTax(this.taxJurisdiction);
return order;
}
buildCustomer(customerId, line1, city, postal) {
const customer = new Customer(customerId);
const address = new Address(line1, city, postal);
customer.setShippingAddress(address);
customer.setBillingAddress(address);
return customer;
}
buildLineItem(item, customer) {
const lineItem = new LineItem(item.product, item.quantity);
lineItem.applyDiscount(customer.discountRate);
return lineItem;
}
}
// Client:
const factory = new OrderFactory(jurisdiction);
const order = factory.createOrder(customerId, line1, city, postal, items);
Example source: Illustrative example written for this site, faithful to Kerievsky's pattern shape in Refactoring to Patterns (Addison-Wesley, 2004), chapter 6. The book pulls complex assembly out of client code into a dedicated factory; this JavaScript version uses an order-with-line-items + customer-with-addresses assembly, distinct from Encapsulate Classes With Factory whose focus is subclass hiding.
Pressure
Human

Duplicated assembly is Shotgun Surgery for every recipe change — a new required field, a defaults update, a wiring change must land everywhere the object is built. Recipe knowledge leaking into clients couples them to the object's internals.

Agent

Recipe duplication × N callers × M recipe steps inflates the agent's context cost on every assembly-related edit. Subtle inconsistencies between caller-side recipes are invisible until they manifest as bugs; static reasoning cannot enforce 'every caller applies the discount'.

Tradeoff
Human

A Factory becomes the single point of coupling to the object's construction shape; signature changes there ripple to every caller. Over-aggressive factory extraction can hide which dependencies the constructed object actually has; debugging requires reading the factory to understand the resulting object.

Agent

A factory hides construction details from the call site; the agent must read the factory to know what the returned object actually carries. Static type information at the call site narrows to the return type, with construction-level invariants pushed inside the factory.

Relief
Human

The recipe is named, tested, and editable in one file. Callers stop knowing what defaults apply, which fields are required, or how sub-collaborators wire. Object construction becomes a domain operation rather than a wiring chore.

Agent

The recipe lives at one factory the agent edits once; callers reach for the factory's named methods, and the recipe cannot drift across callers because callers do not hold a copy of the assembly steps.

Trap
Human

A Factory whose `create` method is itself a long parameter list that mirrors the constructor's just adds an indirection layer. The pattern pays when the recipe involves real assembly decisions (sub-collaborators, defaults, configuration) — not when the factory is a shallow constructor pass-through.

Agent

A factory whose recipe is itself a long sequence of conditional steps can become as opaque as the duplicated callers were — the agent must trace the factory body line-by-line to know what came out. The pattern's gain materializes when the factory's recipe is itself decomposed into named build steps.