Say you have a class User
. The user has an email
, a state
and a list of authentication methods,
among other fields:
public class User {
private String email = "";
private List<String> authenticationMethods = Collections.emptyList();
private State state = State.INITIAL;
public String email() {
return email;
}
public List<String> authenticationMethods() {
return authenticationMethods;
}
public State state() {
return state;
}
}
There’s some initial state invariants. The email cannot be null or empty, the list of of authentication methods
cannot be null and the state is non-null with an initial value for new objects of INITIAL
.
You decide to use a builder and so move the preconditions there:
public class User {
private String email;
private List<String> authenticationMethods;
private State state;
public User(Builder builder) {
this.email = builder.email;
this.authenticationMethods = ImmutableList.copyOf(builder.authenticationMethods);
this.state = builder.state;
}
public String email() {
return email;
}
public List<String> authenticationMethods() {
return authenticationMethods;
}
public State state() {
return state;
}
public static class Builder {
private String email = "";
private List<String> authenticationMethods = new ArrayList<>();
private State state = State.INITIAL;
public Builder() {}
public Builder email(String email) {
this.email = checkNotNullOrEmpty(email);
return this;
}
public Builder addAuthenticationMethod(String authenticationMethod) {
this.authenticationMethods.add(checkNotNullOrEmpty(authenticationMethod));
return this;
}
public Builder state(State state) {
this.state = checkNotNull(state);
return this;
}
public User build() {
return new User(this);
}
}
}
Good. You then want to add a no-arg constructor. Now you have a problem. You can either add the same preconditions to the class fields, as in the builder, in which case you end up with a brittle copy of constants, or break the consistency of the object’s initial state when constructed with the builder vs the no-arg constructor.
There’s a simple way out: use an instance of the Builder
in your no-arg constructors:
public class User {
private String email;
private List<String> authenticationMethods;
private State state;
public User() {
this(new Builder());
}
public User(Builder builder) {
this.email = builder.email;
this.authenticationMethods = ImmutableList.copyOf(builder.authenticationMethods);
this.state = builder.state;
}
...
}