Edit: I’d like to point out that this question describes a theoretical problem, and I am aware that I can use constructor arguments for mandatory parameters, or throw a runtime exception if the API is used incorrectly. However, I am looking for a solution that does not require constructor arguments or runtime checking.
Imagine you have a Car
interface like this:
public interface Car {
public Engine getEngine(); // required
public Transmission getTransmission(); // required
public Stereo getStereo(); // optional
}
As the comments would suggest, a Car
must have an Engine
and Transmission
but a Stereo
is optional. That means a Builder that can build()
a Car
instance should only ever have a build()
method if an Engine
and Transmission
have both already been given to the builder instance. That way the type checker will refuse to compile any code that attempts to create a Car
instance without an Engine
or Transmission
.
This calls for a Step Builder. Typically you’d implement something like this:
public interface Car {
public Engine getEngine(); // required
public Transmission getTransmission(); // required
public Stereo getStereo(); // optional
public class Builder {
public BuilderWithEngine engine(Engine engine) {
return new BuilderWithEngine(engine);
}
}
public class BuilderWithEngine {
private Engine engine;
private BuilderWithEngine(Engine engine) {
this.engine = engine;
}
public BuilderWithEngine engine(Engine engine) {
this.engine = engine;
return this;
}
public CompleteBuilder transmission(Transmission transmission) {
return new CompleteBuilder(engine, transmission);
}
}
public class CompleteBuilder {
private Engine engine;
private Transmission transmission;
private Stereo stereo = null;
private CompleteBuilder(Engine engine, Transmission transmission) {
this.engine = engine;
this.transmission = transmission;
}
public CompleteBuilder engine(Engine engine) {
this.engine = engine;
return this;
}
public CompleteBuilder transmission(Transmission transmission) {
this.transmission = transmission;
return this;
}
public CompleteBuilder stereo(Stereo stereo) {
this.stereo = stereo;
return this;
}
public Car build() {
return new Car() {
@Override
public Engine getEngine() {
return engine;
}
@Override
public Transmission getTransmission() {
return transmission;
}
@Override
public Stereo getStereo() {
return stereo;
}
};
}
}
}
There’s a chain of different builder classes (Builder
, BuilderWithEngine
, CompleteBuilder
), that add one required setter method after another, with the last class containing all optional setter methods as well.
This means that users of this step builder are confined to the order in which the author made mandatory setters available. Here is an example of possible uses (note that they are all strictly ordered: engine(e)
first, followed by transmission(t)
, and finally the optional stereo(s)
).
new Builder().engine(e).transmission(t).build();
new Builder().engine(e).transmission(t).stereo(s).build();
new Builder().engine(e).engine(e).transmission(t).stereo(s).build();
new Builder().engine(e).transmission(t).engine(e).stereo(s).build();
new Builder().engine(e).transmission(t).stereo(s).engine(e).build();
new Builder().engine(e).transmission(t).transmission(t).stereo(s).build();
new Builder().engine(e).transmission(t).stereo(s).transmission(t).build();
new Builder().engine(e).transmission(t).stereo(s).stereo(s).build();
However, there are plenty of scenarios in which this is not ideal for the builder’s user, especially if the builder has not just setters, but also adders, or if the user cannot control the order in which certain properties for the builder will become available.
The only solution I could think of for that is very convoluted: For every combination of mandatory properties having been set or having not yet been set, I created a dedicated builder class that knows which potential other mandatory setters need to be called before arriving at a state where the build()
method should available, and each one of those setters returns a more complete type of builder which is one step closer to containing a build()
method.
I added the code below, but you might say that I’m using the type system to create a FSM that lets you create a Builder
, which can be turned into either a BuilderWithEngine
or BuilderWithTransmission
, which both can then be turned into a CompleteBuilder
, which implements the build()
method. Optional setters can be invoked on any of these builder instances.
public interface Car {
public Engine getEngine(); // required
public Transmission getTransmission(); // required
public Stereo getStereo(); // optional
public class Builder extends OptionalBuilder {
public BuilderWithEngine engine(Engine engine) {
return new BuilderWithEngine(engine, stereo);
}
public BuilderWithTransmission transmission(Transmission transmission) {
return new BuilderWithTransmission(transmission, stereo);
}
@Override
public Builder stereo(Stereo stereo) {
super.stereo(stereo);
return this;
}
}
public class OptionalBuilder {
protected Stereo stereo = null;
private OptionalBuilder() {}
public OptionalBuilder stereo(Stereo stereo) {
this.stereo = stereo;
return this;
}
}
public class BuilderWithEngine extends OptionalBuilder {
private Engine engine;
private BuilderWithEngine(Engine engine, Stereo stereo) {
this.engine = engine;
this.stereo = stereo;
}
public CompleteBuilder transmission(Transmission transmission) {
return new CompleteBuilder(engine, transmission, stereo);
}
public BuilderWithEngine engine(Engine engine) {
this.engine = engine;
return this;
}
@Override
public BuilderWithEngine stereo(Stereo stereo) {
super.stereo(stereo);
return this;
}
}
public class BuilderWithTransmission extends OptionalBuilder {
private Transmission transmission;
private BuilderWithTransmission(Transmission transmission, Stereo stereo) {
this.transmission = transmission;
this.stereo = stereo;
}
public CompleteBuilder engine(Engine engine) {
return new CompleteBuilder(engine, transmission, stereo);
}
public BuilderWithTransmission transmission(Transmission transmission) {
this.transmission = transmission;
return this;
}
@Override
public BuilderWithTransmission stereo(Stereo stereo) {
super.stereo(stereo);
return this;
}
}
public class CompleteBuilder extends OptionalBuilder {
private Engine engine;
private Transmission transmission;
private CompleteBuilder(Engine engine, Transmission transmission, Stereo stereo) {
this.engine = engine;
this.transmission = transmission;
this.stereo = stereo;
}
public CompleteBuilder engine(Engine engine) {
this.engine = engine;
return this;
}
public CompleteBuilder transmission(Transmission transmission) {
this.transmission = transmission;
return this;
}
@Override
public CompleteBuilder stereo(Stereo stereo) {
super.stereo(stereo);
return this;
}
public Car build() {
return new Car() {
@Override
public Engine getEngine() {
return engine;
}
@Override
public Transmission getTransmission() {
return transmission;
}
@Override
public Stereo getStereo() {
return stereo;
}
};
}
}
}
As you can tell, this doesn’t scale well, as the number of different builder classes required would be O(2^n) where n is the number of mandatory setters.
Hence my question: Can this be done more elegantly?
(I’m looking for an answer that works with Java, although Scala would be acceptable too)
14
You seem to have two different requirements, based on the method calls you provided.
- Only one (required) engine, only one (required) transmission, and only one (optional) stereo.
- One or more (required) engines, one or more (required) transmissions, and one or more (optional) stereos.
I think the first issue here is that you don’t know what you want the class to do. Part of that is that it’s not known what you want the built object to look like.
A car can only have one engine and one transmission. Even hybrid cars only have one engine (perhaps a GasAndElectricEngine
)
I’ll address both implementations:
public class CarBuilder {
public CarBuilder(Engine engine, Transmission transmission) {
// ...
}
public CarBuilder setStereo(Stereo stereo) {
// ...
return this;
}
}
and
public class CarBuilder {
public CarBuilder(List<Engine> engines, List<Transmission> transmission) {
// ...
}
public CarBuilder addStereo(Stereo stereo) {
// ...
return this;
}
}
If an engine and transmission are required, then they should be in the constructor.
If you don’t know what engine or transmission is required, then don’t set one yet; it’s a sign that you’re creating the builder too far up the stack.
0
Why not using the null object pattern ? Get rid of this builder, the most elegant code you can write is the one you actually don’t have to write.
public final class CarImpl implements Car {
private final Engine engine;
private final Transmission transmission;
private final Stereo stereo;
public CarImpl(Engine engine, Transmission transmission) {
this(engine, transmission, new DefaultStereo());
}
public CarImpl(Engine engine, Transmission transmission, Stereo stereo) {
this.engine = engine;
this.transmission = transmission;
this.stereo = stereo;
}
//...
}
5
Firstly, unless you have a lot more time than any shop I’ve worked in, it is probably not worth allowing any order of operations or just living with the fact that you can specify more than one radio. Note that you are talking about code, not user input, so you can have assertions that will fail during your unit testing rather than a second before at compile time.
However, if your constraint is, as given in the comments, that you have to have an engine and a transmission, then enforce this by putting all the obligatory properties is the builder’s constructor.
new Builder(e, t).build(); // ok
new Builder(e, t).stereo(s).build(); // ok
new Builder(e, t).stereo(s).stereo(s).build(); // exception on second call to stereo as stereo is already set
If it is only the stereo which is optional, then doing the final step using sub-classes of builders is possible, but beyond that the gain from getting the error at compile time rather than in testing is probably not worth the effort.
0
the number of different builder classes required would be O(2^n) where n is the number of mandatory setters.
You have already guessed the correct direction for this question.
If you want to get compile-time checking, you will need (2^n)
types. If you want to get run-time checking, you will need a variable that can store (2^n)
states; an n
-bit integer will do.
Because C++ supports non-type template parameter (e.g. integer values), it is possible for a C++ class template to be instantiated into O(2^n)
different types, using a scheme similar to this.
However, in languages that don’t support non-type template parameters, you cannot rely on the type system to instantiate O(2^n)
different types.
The next opportunity is with Java annotations (and C# attributes). These additional metadata can be used to trigger user-defined behavior at compile-time, when annotation processors are used. However, it would be too much work for you to implement these. If you are using frameworks that provide this functionality for you, use it. Otherwise, check the next opportunity.
Finally, notice that storing O(2^n)
different states as a variable at run-time (literally, as an integer that has at least n
bits wide) is very easy. This is why the most upvoted answers all recommend you to perform this check at run-time, because the effort needed to implement compile-time checking is too much, when compared to the potential gain.
0