Refactoring techniqueSimplifying Method Calls
Replace Constructor with Factory Method
Wrap object creation in a named static or module function when a plain constructor cannot express intent, pick a subtype, or reuse instances.
- Also known as
- Replace Constructor with Factory Function
Problem
A constructor has to do more than a constructor can express well. Callers pass a flag to get different variants, several constructors differ only by argument order, or creation should sometimes return a cached instance or a subclass. new always returns a fresh object of exactly one class, with no name to explain which variant you get.
Solution
Create a static method (or module-level function) that builds the object, give it a name that describes the variant, make the constructor private, and route callers through the factory.
Before
class Discount {
constructor(
readonly kind: "percent" | "fixed",
readonly value: number,
readonly currency?: string,
) {
if (kind === "percent" && (value <= 0 || value > 100)) throw new RangeError("bad percent");
if (kind === "fixed" && !currency) throw new Error("fixed discount needs a currency");
}
}
const promo = new Discount("percent", 15);
const voucher = new Discount("fixed", 2000, "EUR");After
class Discount {
private constructor(
readonly kind: "percent" | "fixed",
readonly value: number,
readonly currency?: string,
) {}
static percent(rate: number): Discount {
if (rate <= 0 || rate > 100) throw new RangeError("bad percent");
return new Discount("percent", rate);
}
static fixed(amountCents: number, currency: string): Discount {
return new Discount("fixed", amountCents, currency);
}
}
const promo = Discount.percent(15);
const voucher = Discount.fixed(2000, "EUR");Why
- Each way of creating the object has a name and only the arguments it needs.
- Invalid combinations (a percent with a currency, a fixed amount without one) cannot be written.
- The factory can later return a subclass, a cached instance or a result type, without changing callers.
Steps
- Create a static factory that calls the existing constructor with the same arguments. Run the tests.
- Replace
newcalls with the factory, one module at a time. Run the tests. - Make the constructor
private(or unexported). The compiler now finds any missednewcalls. - Split the factory by variant, moving variant-specific validation into each one. Run the tests.
Watch out
- Do not add a factory when a plain constructor with clear parameters works.
new Point(x, y)needs noPoint.of. - Private constructors block subclassing in some languages and break frameworks that instantiate classes reflectively (ORMs, DI containers, serializers). Check before you hide the constructor.
- A factory that grows a
switchover a type string is just moving the problem. Use Replace Type Code with Subclasses and let each subclass have its own factory, or a map. - The inverse, replacing a factory with a constructor, has no named technique. Do it when the factory adds nothing beyond
new.
Related
- Treats Switch Statements and Primitive Obsession on type-code constructor arguments.
- Supports Remove Setting Method by centralizing initialization.
- Not the same as the Factory Method pattern, which uses subclass overrides. See also Abstract Factory.
Agent checklist
- The constructor needed a flag, had ambiguous overloads, or must return something other than a fresh instance.
- Each factory has a name that describes the variant it creates.
- No framework requires the constructor to stay public.
- All
newcalls outside the class were migrated before the constructor went private.
Treats these smells
See also
- Remove Setting MethodDelete the setter for a field that should only be set at creation, and assign it in the constructor so it cannot change later.
- Replace Type Code with SubclassesTurn each value of an immutable type code that drives conditionals into a subclass, and move the branch logic into overrides.
- Factory MethodLet a subclass or a supplied function decide which concrete object to create, so shared workflow code depends only on an interface.
- Abstract FactoryCreate families of related objects through one interface so client code always gets a matching set and never names the concrete classes.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.