Instance Factory Methods
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
| Situation | Use |
|---|---|
| One obvious way to build it | Constructor |
| Several ways with the same parameter types | Static factories with distinct names |
| The units or meaning need naming | Static factory |
| You may return a subclass | Static factory |
| You may reuse instances | Static factory |
| Creation depends on shared configuration | Instance factory |
| Creation can fail without an exception | Static factory returning Optional |
Naming conventions
The standard library is consistent, and following it makes your code predictable:
| Name | Meaning | Example |
|---|---|---|
of | Build from the given components | List.of(...) |
from / valueOf | Convert from another type | Integer.valueOf("5") |
getInstance | Return an instance, possibly shared | Calendar.getInstance() |
create / newInstance | Always a fresh object | — |
copyOf | A copy of the argument | List.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 thatnew Angle(90)did not. - Forgetting
staticon 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
- Write an
Angleclass with a private constructor andofDegrees/ofRadiansfactories, plus agetDegrees()method. Confirm both routes agree. - Add a
ZEROconstant and makeofRadians(0)return it. Verify with==that you get the same object twice. - Write a
Temperatureclass withfromCelsiusandfromFahrenheitfactories. - Write an abstract
Sensorwith two subclasses, and a static factory choosing between them from aString. Confirm the caller only ever seesSensor. - Write a
MotorFactoryholding shared configuration and creating configuredMotorobjects from a port number.
Hints
- Store radians internally; convert on the way in and out.
- A
static finalfield, returned when the argument is 0. - Same shape as
Angle. Pick one internal unit and convert at the boundary. - 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.
- Constructor takes the configuration;
create(int port)applies it.