A logbook for curious waters
← Software Engineering Field Guide

Replace Constructor with Factory Method

Before you begin: Constructors, static methods, and units of measurement.

Replace Constructor with Factory Method puts creation behind a named operation. The modern refactoring name, Replace Constructor with Factory Function, helps distinguish it from the separate Gang of Four Factory Method design pattern. A named factory does not require subclassing. 1

Original version: what unit does the constructor expect?

The distance class stores whole meters. Callers holding kilometers perform the conversion themselves. Values in this example are small enough to avoid integer overflow.

public sealed class Distance
{
    public int Meters { get; }
    public Distance(int meters) { Meters = meters; }
}
var distance = new Distance(3 * 1000);
Console.WriteLine(distance.Meters); // 3000
class Distance:
    def __init__(self, meters):
        self.meters = meters
distance = Distance(3 * 1000)
print(distance.meters)  # 3000

The constructor name cannot tell the reader whether a bare 3 means meters or kilometers. Its parameter declaration can, but the call requires extra knowledge.

Refactored version: the creation name states the unit

public sealed class Distance
{
    public int Meters { get; }
    private Distance(int meters) { Meters = meters; }
    public static Distance FromMeters(int meters)
    {
        return new Distance(meters);
    }
    public static Distance FromKilometers(int kilometers)
    {
        return new Distance(kilometers * 1000);
    }
}

Distance.FromKilometers(3) names the conversion and performs it in one place. The private constructor directs ordinary callers through the named factories.

class Distance:
    def __init__(self, meters):
        self.meters = meters

    @classmethod
    def from_meters(cls, meters):
        return cls(meters)

    @classmethod
    def from_kilometers(cls, kilometers):
        return cls(kilometers * 1000)

Distance.from_kilometers(3) states the input unit. classmethod receives the class as cls, allowing the factory to construct an instance. Python’s direct constructor remains callable; the factories provide the preferred explicit vocabulary.

A factory creates flexibility, not a requirement to use all of it

Factories can validate input, choose an implementation, or return a cached instance. Each choice has consequences. Returning a shared mutable object instead of a fresh instance, for example, changes identity and ownership behavior.

This example only centralizes construction and conversion. It does not introduce caching or different subtypes. Existing calls for three kilometers preserve their 3,000-meter result.

Use a named factory when creation has meaningful alternatives or the constructor’s arguments are ambiguous. A plain constructor is still appropriate when it already communicates the operation clearly.

Sources

  1. Martin Fowler — Replace Constructor with Factory Function.
  2. Python — classmethod.