Instance Factory Methods

intermediate25 min

What constructors cannot do

A constructor has to be named after its class. That is fine until you need two ways of building the same thing:

class Angle {
    private final double radians;

    Angle(double radians) { this.radians = radians; }
    Angle(double degrees) { ... }      // will not compile
}

Both take a double, so they have the same signature. Java cannot distinguish them, and no amount of overloading helps.

A factory method solves it by using a name instead:

class Angle {
    private final double radians;

    private Angle(double radians) {          // private: not called directly
        this.radians = radians;
    }

    static Angle ofRadians(double radians) {
        return new Angle(radians);
    }

    static Angle ofDegrees(double degrees) {
        return new Angle(Math.toRadians(degrees));
    }
}

Now the call says what it means:

Angle a = Angle.ofDegrees(90);
Angle b = Angle.ofRadians(Math.PI / 2);

Compare that with new Angle(90), where a reader has to check the constructor to learn which unit is expected. Unit confusion is a real source of robot bugs, and naming the method removes the ambiguity.

Making the constructor private

Marking the constructor private forces everyone through the factory methods. That is what gives you control — you can validate, cache, or change what you return without breaking any caller.

If you leave the constructor public, both routes exist and the guarantees weaken. Make it private unless you have a reason not to.

Static factory versus instance factory

Both are called factory methods, and the difference is what they belong to.

A static factory belongs to the class being created, as above. Angle.ofDegrees(90).

An instance factory method belongs to some other object, and that object’s state influences what gets built:

class MotorFactory {
    private final boolean inverted;
    private final double maxOutput;

    MotorFactory(boolean inverted, double maxOutput) {
        this.inverted = inverted;
        this.maxOutput = maxOutput;
    }

    Motor create(int port) {
        Motor m = new Motor(port);
        m.setInverted(inverted);
        m.setMaxOutput(maxOutput);
        return m;
    }
}
MotorFactory driveMotors = new MotorFactory(true, 0.8);

Motor left  = driveMotors.create(1);
Motor right = driveMotors.create(2);

The shared configuration lives in the factory, so you set it once rather than repeating it at every creation site. Add a third motor later and it is automatically configured the same way.

This is the form that pairs with dependency injection — you pass a factory to code that needs to create things without deciding how they are configured.

Returning a subtype

The most powerful consequence: a factory can return something more specific than it promises.

abstract class Drivetrain {
    abstract void drive(double speed, double rotation);
}

class TankDrive extends Drivetrain { ... }
class SwerveDrive extends Drivetrain { ... }

class DrivetrainFactory {
    static Drivetrain forRobot(String config) {
        if (config.equals("swerve")) {
            return new SwerveDrive();
        }
        return new TankDrive();
    }
}

Callers get a Drivetrain and never learn which kind:

Drivetrain drive = DrivetrainFactory.forRobot(config);
drive.drive(0.5, 0.0);            // whichever implementation, same call

Runtime polymorphism does the dispatch. The decision about which class to build is made in exactly one place, and every user of Drivetrain is unaffected by it.

Other things factories can do

Validate before constructing. A constructor that throws leaves callers with an awkward failure; a factory can return an Optional or a sensible fallback.

static Optional<Angle> parse(String text) {
    try {
        return Optional.of(ofDegrees(Double.parseDouble(text)));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

Reuse instances. A constructor must produce a new object every time. A factory may hand back a cached one:

class Angle {
    static final Angle ZERO = new Angle(0);

    static Angle ofRadians(double radians) {
        if (radians == 0) return ZERO;         // no new object
        return new Angle(radians);
    }
}

This is why Integer.valueOf(5) is preferred over new Integer(5) — the factory caches small values, and the constructor cannot.

Hide generic clutter.

// the standard library does exactly this
List<String> names = List.of("a", "b", "c");
Map<String, Integer> ports = Map.of("intake", 3);

Constructor or factory

SituationUse
One obvious way to build itConstructor
Several ways with the same parameter typesStatic factories with distinct names
The units or meaning need namingStatic factory
You may return a subclassStatic factory
You may reuse instancesStatic factory
Creation depends on shared configurationInstance factory
Creation can fail without an exceptionStatic factory returning Optional

Naming conventions

The standard library is consistent, and following it makes your code predictable:

NameMeaningExample
ofBuild from the given componentsList.of(...)
from / valueOfConvert from another typeInteger.valueOf("5")
getInstanceReturn an instance, possibly sharedCalendar.getInstance()
create / newInstanceAlways a fresh object
copyOfA copy of the argumentList.copyOf(...)

Do not overuse it

A factory for a class with one straightforward constructor is pure overhead. new Point(3, 4) is perfectly clear, and wrapping it in Point.of(3, 4) adds a layer for nothing.

Add a factory when you have a concrete reason from the table above — naming, subtype choice, validation, or reuse. “Factories are good practice” is not a reason.

Common mistakes

  • Leaving the constructor public, so the factory can be bypassed.
  • Unclear names. Angle.make(90) says nothing that new Angle(90) did not.
  • Forgetting static on a static factory, making it impossible to call without an instance.
  • A factory returning the concrete type when the point was to hide it. Return the abstract type.
  • Mutable cached instances. Only share objects that cannot be changed after creation, or callers will interfere with each other.

Practice

  1. Write an Angle class with a private constructor and ofDegrees / ofRadians factories, plus a getDegrees() method. Confirm both routes agree.
  2. Add a ZERO constant and make ofRadians(0) return it. Verify with == that you get the same object twice.
  3. Write a Temperature class with fromCelsius and fromFahrenheit factories.
  4. Write an abstract Sensor with two subclasses, and a static factory choosing between them from a String. Confirm the caller only ever sees Sensor.
  5. Write a MotorFactory holding shared configuration and creating configured Motor objects from a port number.
Hints
  1. Store radians internally; convert on the way in and out.
  2. A static final field, returned when the argument is 0.
  3. Same shape as Angle. Pick one internal unit and convert at the boundary.
  4. Return the abstract type from the factory. Try calling a subclass-only method on the result and note the compile error — that is the encapsulation working.
  5. Constructor takes the configuration; create(int port) applies it.

Next

Related