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인 객체는 배타적 제어 없이 액세스 할 수 있다고 하는 큰 이점을 가지고 있다. 그 결과적으로 속도적으로도 유리하게 되는 경우가 많다.




최종 수정 : 2023-05-06