Effective Java - Item 2. Consider a Builder When a Constructor Has Many Parameters
Initialization with constructors
If a constructor needs to receive many parameters, you may have to write many constructors depending on the combinations of parameters. Not only is maintaining many constructors difficult, but when looking at caller code, it is hard to tell what each parameter represents.
// 페라미터의 의미가 알 수가 없다
StreamInfo info1 = new StreamInfo(200, 150, true, false);
StreamInfo info2 = new StreamInfo(200, 150, false);
Initialization with the JavaBeans pattern
As a way to initialize multiple fields, you can also create an instance with a constructor that receives no parameters and then call multiple setters to set the fields.
// JavaBeans 패턴
StreamInfo info = new StreamInfo();
info.setWidth(200);
info.setHeight(150);
info.setCaption(true);
info.setProgressive(false);
Setting field values this way makes it clear which value is assigned to which field. However, providing setters means the object cannot be immutable, making it difficult to create a class that can be handled safely in a multithreaded design.
Initialization with the Builder pattern
This is where the builder pattern comes in. In the Builder pattern, you provide a Builder object that holds the parameters for instance creation, and the target object must be created through this object. Because the Builder class provides named setter methods, the code that sets each parameter becomes easier to understand.
// Builder 패턴으로 생성할 수 있도록 한 StreamInfo 클래스
public class StreamInfo {
private final int width;
private final int height;
private final boolean hasCaption;
private final boolean isProgressive;
/** To create an instance of StreamInfo, use Builder#build() instead. */
private StreamInfo(Builder builder) {
width = builder.width;
height = builder.height;
hasCaption = builder.hasCaption;
isProgressive = builder.isProgressive;
}
public int getWidth() { return width; }
public int getHeight() { return height; }
public boolean hasCaption() { return hasCaption; }
public boolean isProgressive() { return isProgressive; }
public static class Builder {
private int width;
private int height;
private boolean hasCaption;
private boolean isProgressive;
public Builder setWidth(int width) {
this.width = width;
return this;
}
public Builder setHeight(int height) {
this.height = height;
return this;
}
public Builder setCaption(boolean hasCaption) {
this.hasCaption = hasCaption;
return this;
}
public Builder setProgressive(boolean isProgressive) {
this.isProgressive = isProgressive;
return this;
}
public StreamInfo build() {
// If necessary fields have not been set yet,
// IllegalStateException can be thrown here.
return new StreamInfo(this);
}
}
}
An example of creating an instance with a Builder object is shown below.
StreamInfo info = new StreamInfo.Builder().setWidth(200).setHeight(150)
.setCaption(true).setProgressive(false).build();
The generated StreamInfo object has no setters, so it is immutable and thread-safe. The Builder object can be reused, so it is also useful when you want to create objects where only some fields differ.
The disadvantage of the builder pattern is that it causes a slight performance cost because each field is copied when the object is created. However, in multithreaded programming, immutable objects have the major advantage that they can be accessed without exclusive control. As a result, they are often advantageous in terms of speed as well.