The builder pattern and a large number of mandatory parameters

To date I use the following implementation of the builder pattern (as opposed to the implementation described here):

public class Widget {
public static class Builder {
public Builder(String name, double price) { ... }
public Widget build() { ... }
public Builder manufacturer(String value) { ... }
public Builder serialNumber(String value) { ... }
public Builder model(String value) { ... }
}


private Widget(Builder builder) { ... }
}

This works well for most situations I've encountered where I need to build up a complex object with a variety of required/mandatory and optional parameters. However, I've been struggling lately to understand how the pattern is of any benefit when all your parameters are mandatory (or at least the vast majority are).

One means of getting around this has been to logically group the parameters being passed in to their own classes to reduce the number of parameters being passed to the builder constructor.

For example, instead of:

Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
.addOptional(opt9)
.build();

becomes grouped as follows:

Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);


Widget example2 = new Widget.Builder(group1, group2, req7, req8)
.addOptional(opt9)
.build();

While having separate objects simplifies things quite a bit, it also makes things a little difficult to follow if one is not familiar with the code. One thing I considered was moving all parameters into their own addParam(param) methods and then performing validation on required parameters in the build() method.

What is best practice and is there perhaps a better approach to this that I haven't considered?

50074 次浏览

A builder/factory still lets you decouple interface from implementation type (or lets you plug in an adapter, etc), assuming Widget becomes an interface and you have a way to inject or hide the new Widget.Builder.

If you don't care about decoupling, and your implementation is a one-off, then you're right: the builder pattern isn't much more useful than a normal constructor (it still labels its arguments with the attribute-per-builder-method style.)

If you are repeatedly creating objects with little variation in the arguments, then it may still be helpful. You could pass in, cache, etc the intermediate builder acquired after plugging in several attributes:

Widget.Builder base = new Widget.Builder(name, price).model("foo").manufacturer("baz");


// ...


Widget w1 = base.serialNumber("bar").build();
Widget w2 = base.serialNumber("baz").build();
Widget w3 = base.serialNumber("quux").build();

This assumes that your builders are immutable: builder setters don't set an attribute and returning this, but return a new copy of themselves with the change instead. As you pointed out above, parameter objects are another way to get around repeated argument boilerplate. There, you don't even need the builder pattern: just pass the parameter object to your implementation constructor.

I've been struggling lately to understand how the pattern is of any benefit when all your parameters are mandatory

The pattern eases creation of immutable classes and promotes readable code. Consider the Person class below (with a conventional constructor and a builder).

public static class Person {


private static final class Builder {
private int height, weight, age, income, rank;
public Builder setHeight(final int height) { this.height = height; return this; }
public Builder setWeight(final int weight) { this.weight = weight; return this; }
public Builder setAge(final int age) { this.age = age; return this; }
public Builder setIncome(final int income) {    this.income = income; return this; }
public Builder setRank(final int rank) { this.rank = rank; return this; }
public Person build() { return new Person(this); }
}


private final int height;
private final int weight;
private final int age;
private final int income;
private final int rank;


public Person(final int height, final int weight, final int age, final int income, final int rank) {
this.height = height; this.weight = weight; this.age = age; this.income = income; this.rank = rank;
}


private Person(final Builder builder) {
height = builder.height; weight = builder.weight; age = builder.age; income = builder.income; rank = builder.rank;
// Perform validation
}


public int getHeight() { return height; }
public int getWeight() { return weight; }
public int getAge() { return age; }
public int getIncome() { return income; }
public int getRank() {  return rank; }


}

Which method of construction is easier to comprehend?

final Person p1 = new Person(163, 184, 48, 15000, 23);
final Person p2 = new Person.Builder().setHeight(163).setWeight(184).setAge(48).
setIncome(15000).setRank(23).build();

One means of getting around this has been to logically group the parameters being passed in to their own classes

Sure, this is the principle of cohesion and should be adopted irrespective of object construction semantics.

However, I've been struggling lately to understand how the pattern is of any benefit when all your parameters are mandatory (or at least the vast majority are).

The fluent builder pattern is still beneficial:

  1. Its more readable - it effectively allows named parameters so that the call isn't just a long list of unnamed arguments

  2. Its unordered - this lets you group arguments together into logical groups, either as part of a single builder setter call or simply by letting you use a natural order to calling the builder setter methods that make the most sense of this particular instantiation.


Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
.addOptional(opt9)
.build();

becomes grouped as follows:

Object1 group1  = new Object1(req1, req2, req3, req4);
Object2 group2  = new Object2(req5, req6);
Widget example2 = new Widget.Builder(group1, group2, req7, req8)
.addOptional(opt9)
.build();

While having separate objects simplifies things quite a bit, it also makes things a little difficult to follow if one is not familiar with the code. One thing I considered was moving all parameters into their own addParam(param) methods and then performing validation on required parameters in the build() method.

I would favor a hybrid when appropriate or natural. It doesn't have to be all in constructor or each param has its own addParam method. Builder gives you flexibility to do one, the other, in-between, or a combo:

Widget.Builder builder = new Widget.Builder(Widget.BUTTON);


builder.withWidgetBackingService(url, resource, id);
builder.withWidgetStyle(bgColor, lineWidth, fontStyle);
builder.withMouseover("Not required");


Widget example = builder.build();

One advantage of the Builder Pattern that I rarely (if ever) see promoted is that it can also be used to conditionally construct the object, for instance only if all mandatory parameters are correct or if other required resources are available. In that respect they offer similar benefits to a static factory method.

You can use a Step Builder if you have many mandatory parameters. In short: you define an interface for every single mandatory parameter and a builder method returns the next mandatory builder interface or the builder itself for optional methods. The builder remains a single class which implements all the interfaces.

interface StepB {
StepBuilder b(String b);
}


interface StepA {
StepB a(String a);
}


final class StepBuilder implements StepA, StepB {
private String a;
private String b;
private String c = "";


private StepBuilder() {
}


static StepA with() {
return new StepBuilder();
}


// mandatory, from StepA
@Override
StepB a(String a) {
this.a = a;
return this;
}


// mandatory, from StepB
@Override
StepBuilder b(String b) {
this.b = b;
return this;
}


// optional
StepBuilder c(String c) {
this.c = c;
return this;
}


Product build() {
return new Product(a, b, c);
}
}

Usage:

StepBuilder.with().a("hello").b("world").build();


// or with the optional parameter c
StepBuilder.with().a("hello").b("world").c("!").build();

Languages like Kotlin and Scala are more convenient here, since they offer named parameters with default values.

I think this would be appropriate in case you have large mandatory values, though number of interfaces would increase but code would be clean

public class PersonBuilder implements NamePersonBuilder, LastNamePersonBuilder,
BirthDatePersonBuilder, FinalPersonBuilder {


private String name;
private String lastName;
private Date birthDate;
private String phoneNumber;


/**
* Private constructor to force the use of the factroy method
*/
private PersonBuilder() {
}


/**
* Creates a new person builder
*/
public static NamePersonBuilder aPerson() {
return new PersonBuilder();
}


public LastNamePersonBuilder withName(String aName) {
name = aName;
return this;
}


public BirthDatePersonBuilder withLastName(String aLastName) {
lastName = aLastName;
return this;
}


public FinalPersonBuilder withBirthDate(Date aBirthDate) {
birthDate = aBirthDate;
return this;
}


public FinalPersonBuilder andPhoneNumber(String aPhoneNumber) {
phoneNumber = aPhoneNumber;
return this;
}


public Person build() {
// The constructor and setters for Person has default scope
// and is located in the same package as the builder
Person p = new Person();
p.setName(name);
p.setLastName(lastName);
p.setBirthDate(birthDate);
p.setPhoneNumber(phoneNumber);
return p;
}


interface NamePersonBuilder {
LastNamePersonBuilder withName(String aName);
}


interface LastNamePersonBuilder {
BirthDatePersonBuilder withLastName(String aLastName);
}


interface BirthDatePersonBuilder {
FinalPersonBuilder withBirthDate(Date aBirthDate);
}


interface FinalPersonBuilder {
FinalPersonBuilder andPhoneNumber(String aPhoneNumber);
Person build();
}}

This will force the user to set all mandatory values and also force the order that the values are set. So to construct a person this will be the resulting code:

PersonBuilder.aPerson()
.withName("Name")
.withLastName("LastName")
.withBirthDate(new Date())
.build();

Checkout this Reference: Builder Pattern with Twist

My solution with anonymous class. Here is familyName is required parameter, and givenName is Optional. The main target if this solution is force the programmer who creating Person to set requires parameters (if he will not do it, Java will not compile).

new Person(
Person.parametersObject(new Person.RequiredParameters() {
@Override
public void setFamilyName() {
this.familyName = "Jonson";
}
})
.setGivenName("John")
);

Actually, the target is not reached completely: because I couldn't to force the programmer to write this.familyName = familyName;, but he must to implement the setFamilyName. If programmer is not imbecile, he knows what he must to do in this method, but he can forget it because of fatigue.

Implementation:

public class Person {


private String familyName;
private String givenName;




public Person(ParametersObject parametersObject) {
parametersObject.initializeSpecifiedFields(this);
}


public static ParametersObject parametersObject(Person.RequiredParameters requiredParameters) {
return new Person.ParametersObject(requiredParameters);
}




public String getFamilyName() {
return familyName;
}
public Person setFamilyName(String familyName) {
this.familyName = familyName;
return this;
}


public String getGivenName() {
return givenName;
}
public Person setGivenName(String givenName) {
this.givenName = givenName;
return this;
}




public static class ParametersObject {


private String familyName;
private String givenName;


public ParametersObject(Person.RequiredParameters requiredParameters) {
this.familyName = requiredParameters.familyName;
}


public void initializeSpecifiedFields(Person person) {
person.familyName = this.familyName;
person.givenName = this.givenName;
}


public ParametersObject setGivenName(String givenName) {
this.givenName = givenName;
return this;
}
}


public static abstract class RequiredParameters {
public String familyName;
public abstract void setFamilyName();
}
}

I just released a free Intellij plugin to tackle this kind of problem. Essentially you define a method for each parameter in your builder and you can annotate which ones are mandatory and which ones are not and the IntelliJ completion will highlight what are mandatory and optional parameters. Feel free to give it a try:

https://github.com/banterly91/Java-Builder-Guided-Completion-Intellij-Plugin