Builder pattern that requires at least two properties to be specified
I am writing a RequestBuilder
class, which will handle the creation of a query string, based on the following criteria
- category (
String
) - country (
String
) - keywords (
String[]
) - page (
int
) - pageSize (
int
)
Since not all criteria are mandatory and there are many combinations between them (I counted 7, of which only four should be valid - see below why), I decided to use the builder pattern:
public class RequestBuilder {
private String category = "";
private String country = "&country=us";
private String keywords = "";
private String page = "";
private String pageSize = "&pageSize=100";
public RequestBuilder() {
}
private String buildQuery() {
return this.category + this.country + this.keywords + this.page + this.pageSize;
}
// the setter methods, which I omitted for readability
But there is a problem. I need to force the user to specify at least two of either category
, country
or keywords
before building the object(right now the user isn't obliged to specify even one!). A user shouldn't be able to create an object by specifying only country
, for example.
So how do I force this requirement? If I make three constructors(each having two of those parameters) I feel like I am ruining the Builder pattern, even though there will be three more optional properties to specify.
As a designer, you need to decide what fields are really required. There is no such thing as "maybe required". To use Builder Pattern and enforce required parameters, mark the fields as final
and inject them through the constructor:
public class Request {
// fields
private final String requiredField;
private final String optional1;
private final String optional2;
private Request(RequestBuilder builder) {
requiredField = builder.requiredField;
optional1 = builder.optional1;
optional2 = builder.optional2;
}
// add only getter method to the Request class (builds immutable Request objects)
public static class RequestBuilder {
private final String requiredField;
private String optional1;
private String optional2;
public RequestBuilder(String requiredField) {
this.requiredField = requiredField;
}
public RequestBuilder setOptional1(String optional1) {
this.optional1 = optional1;
return this;
}
public RequestBuilder setOptional2(String optional2) {
this.optional2 = optional2;
return this;
}
public Request build() {
return new Request(this);
}
}
}
The builder enforces required and optional fields. The object being built by the builder hides the constructor so that it is only accessible via the builder. The fields inside the request object are all final for immutability.
To use, you'll do something like this:
RequestBuilder builder = new RequestBuilder("required");
Request request = builder.setOptional1("foo").setOptional2("bar").build();
or you could simply call build()
at any time after calling the builder constructor.
UPDATE:
Now to your problem.... You could (potentially) modify the build()
to check how many "semi-required" fields you have with values and compare it to the total number of fields. To me, this is a hack. For this, you have two options
- Hard code the number of fields and check how many out of the total number are still null or empty. If the number of fields that are not set is below a certain count, throw some exception (i.e.
InvalidRequiredFieldCount
). Otherwise, you return the new instance. For this, you need to increment the "count" every time a setter method is called. - Use reflection to get the list (array) of fields and use this field and use this field count to calculate the minimum number of "required" fields. Throw exception if that minimum is not reach or return a new request instance if the minimum threshold is reached.
public Request build() throws Exception {
Request request = new Request(this);
int count = 0;
int max = 2;
Field[] allFields = Request.class.getDeclaredFields();
for (Field field : allFields) {
Object o = field.get(request);
if (o != null) {
count++;
}
}
if (count < 2) {
throw new Exception("Minimum number of set fields (2) not reached");
}
return request;
}
This is not pretty, but it works. If I run this:
RequestBuilder builder = new RequestBuilder("required");
Request request = builder.build();
will result in an exception:
Exception in thread "main" java.lang.Exception: Minimum number of set fields (2) not reached
at com.master.oxy.Request$RequestBuilder.build(Request.java:54)
at com.master.oxy.Request.main(Request.java:63)
However, if I set at least one optional, the new instance will be returned.