Effective Java - Item 2. 생성자에 매개변수가 많다면 빌더를 고려하라
생성자를 사용하여 초기화
constructor 으로 많이 파라미터를 받아야 한다면, 파라미터의 조합에 의해 많은 constructor 을 작성하지 않으면 안될 수도 있다. 많은 생성자를 유지 관리하는 것이 어려울 뿐만 아니라, 생성자 호출자의 코드를 보면 각 매개 변수가 무엇을 나타내는지 알기 어렵다는 문제가 발생한다.
// 페라미터의 의미가 알 수가 없다
StreamInfo info1 = new StreamInfo(200, 150, true, false);
StreamInfo info2 = new StreamInfo(200, 150, false);
JavaBeans Pattern 사용하여 초기화
복수 필드의 초기화 방법으로서는 constructor 에서는 파라미터를 받지 않고, 인스턴스를 생성한 후에 복수의 setter 를 호출하는 것으로 필드를 설정하는 방법도 생각할 수 있다.
// JavaBeans 패턴
StreamInfo info = new StreamInfo();
info.setWidth(200);
info.setHeight(150);
info.setCaption(true);
info.setProgressive(false);
이 방법으로 필드의 값을 설정하면 어느 값이 어떤 필드에 대해 설정되어 있는지 명확하게 된다. 그러나 setter 를 제공한다는 것은 그 객체를 불변(Immutable)으로 할 수 없는 것을 나타내고 있어, 멀티스레드 설계에 있어서 안전하게 취급할 수 있는 클래스를 만드는 것이 어렵게 된다.
Builder Pattern을 사용하여 초기화
그래서 빌더 패턴이다. Builder 패턴에서는 인스턴스 생성용의 파라미터를 보관 유지하는 Builder
객체를 제공하고, 반드시 이 객체 사용하여 생성하고자 하는 객체를 생성하도록 한다. Builder
클래스에는 이름 붙은 setter 메소드를 제공하기 위해, 각 파라미터를 설정하는 코드를 알기 쉬워진다.
// 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);
}
}
}
Builder
객체를 사용한 인스턴스 생성 예는 다음과 같다.
StreamInfo info = new StreamInfo.Builder().setWidth(200).setHeight(150)
.setCaption(true).setProgressive(false).build();
생성된 StreamInfo
객체에는 setter가 없으므로, Immutable이며 스레드로부터 안전하다. Builder
객체는 재활용이 가능하기에, 필드의 일부만 다른 객체를 생성하려는 경우에도 활용할 수 있다.
빌더 패턴의 단점은 객체 생성 시 각 필드를 복사하기 위해 약간의 성능 저하를 초래한다는 것이다. 다만, 멀티스레드 프로그래밍에 있어서, Immutable인 객체는 배타적 제어 없이 액세스 할 수 있다고 하는 큰 이점을 가지고 있다. 그 결과적으로 속도적으로도 유리하게 되는 경우가 많다.
배타적 제어란?
여러 사람이 한 물건을 공유하는 상황에 발생한다. 동시에 사용할 시에 고장날 수 있다.병렬 처리에서 필요하여, 베타적 제어 부분은 병목 현사잉 발생할 수 있다.