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.
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.
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);
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.
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.
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.
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.