1

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.

hfontanez
  • 5,774
  • 2
  • 25
  • 37
Kotaka Danski
  • 451
  • 1
  • 4
  • 14

2 Answers2

4

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

  1. 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.
  2. 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.

hfontanez
  • 5,774
  • 2
  • 25
  • 37
  • 1
    @DanielHalachev even though this modification to `build()` could be viewed as "cool", "clever", etc., I want to reiterate this is a terrible idea. I would advise you NOT to do it. Stick to the correct form of builder pattern with clear required and optional fields. BUT, if you MUST do it, I am glad I was able to provide this to you. – hfontanez Jan 21 '22 at 13:02
1

I would like to suggest another object-oriented solution for that problem:

Let's assume you don't want to pass the required arguments to the builder c'tor. You can use the following technique to enforce providing the required field during the build process of the object:

Usage - demonstrate how only the required field is visible first:

enter image description here

Usage2 - demonstrate how the rest of the optional and build() methods are visible after providing the required field:

enter image description here

We implement it by doing the following:

public static class RequestBuilder implements RequestBuilderRequiredField {

public interface RequestBuilderRequiredField {
  RequestBuilder setRequiredField(String requiredField)
}

private final String requiredField;
private String optional1;
private String optional2;

private RequestBuilder() {
}

public static RequestBuilderRequiredField aRequestBuilder() {
  return new RequestBuilder();
}
  • Note the the builder c'tor is private because we want to expose first the interface with the required field (and if have many, we want to chain the interfaces methods so they will return an interface for for each required field)

The downside of that approach is that you need to maintain the same amount of interfaces on a large object when many (or even all) properties are required.

Interesting to think if libraries such as Lombok that auto generates Builder with @Builde annotation can have the ability to generate it

  • 1
    This doesn't make much sense. Why would you put a builder behind an interface if not to (potentially) create a family of builders that can be replaced for one another? The implementation of a builder is not like the implementation of a list, where the algorithm (behavior) changes. What you are showing here is implemented by using a `Factory Pattern`, not by implementing interfaces. – hfontanez Sep 10 '22 at 15:23