3 minute read

item2 생성자에 매개변수가 많다면 빌더를 고려하라.

정적 팩터리와 생성자는 선택적 매개변수가 많을수록 대응하기 힘들다.

점층적 생성자 패턴 (telescoping constructor pattern)

public class Coffee {
    private int coffeeId;
    private String coffeeName;
    private int shots;
    private String size;
    ...

    public Coffee(int coffeeId, String coffeeName) {
        this.coffeeId = coffeeId;
        this.coffeeName = coffeeName;
    }

    public Coffee(int coffeeId, String coffeeName, int shots) {
        this.coffeeId = coffeeId;
        this.coffeeName = coffeeName;
        this.shots = shots;
    }
	...
}

  • 필수 매개변수만 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자, 선택 매개변수를 2개까지 받는 생성자, … 선택 매개변수를 전부 받는 생성자를 작성하는 방식
  • 매개변수 개수가 많아지면 클라이언트 코드를 작성하기 힘들고 가독성 떨어짐

자바 빈즈 패턴(JavaBeans pattern)

Coffee coffee = new Coffee();
coffee.setName("Americano");
coffee.setShots(1);
coffee.setSize("Small");
...
  • 매개변수 없는 생성자로 객체 생성 후, setter()메서드들을 호출해 원하는 매개변수의 값 설정
  • 한 객체를 생성하기 위해 메서드 여러개 호출해야하고(비용이 높아짐), 이 객체가 완전히 생성되기 전까지는 일관성 유지되지 않음
  • 클래스를 불변으로 만들 수 없으므로 스레드 안정성을 위한 추가 작업이 필요

빌더 패턴

public class Coffee {
    private final int coffeeId;
    private final String coffeeName;
    private final int shots;
    private final String size;

    // Private 생성자로 외부에서 직접 객체를 생성하지 못하게 하고,
    // 해당 객체의 Builder 객체로만 생성이 가능하게 한다.
    private Coffee(Builder builder) {
        this.coffeeId = builder.coffeeId;
        this.coffeeName = builder.coffeeName;
        this.shots = builder.shots;
        this.size = builder.size;
    }

    public static class Builder { // 빌더 클래스를 따로 생성. 정적 클래스이다.
	    // 필수 매개변수
        private final int coffeeId;
        private final String coffeeName;
	    
	    // 선택 매개변수 : 기본값으로 초기화할수 있다.
        private int shots = 0;
        private String size = "Small"; 
    
        public Builder(int coffeeId, String coffeeName) { 
        // 필수 매개변수
            this.coffeeId = coffeeId;
            this.coffeeName = coffeeName;
        }

        public Builder shots(int shots) {
            this.shots = shots;
            return this;
        }

        public Builder size(String size) {
            this.size = size;
            return this;
        }

        // Coffee 객체를 생성하여 반환한다.
        public Coffee build() {
            return new Coffee(this);
        }
    }

}
  • 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻을 수 있음
  • 일종의 세터 메서드들을 통해 선택 매개변수들을 설정하고, 마지막에 build 메소드를 호출해 객체를 얻음
Coffee coffee = new Coffee.builder(1, "Espresso")
                     .shots(2)
                     .size("Large")
                     .build();

  • 메서드 체이닝(method chaining)을 통해 가독성 있게 객체를 생성할 수 있다.
  • 불변인 클래스를 생성한다.

빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 적합하다.

public abstract class Member {
    // 모든 서브 타입 객체에 공통적으로 필요한 타입
    public enum Authority { READ, WRITE, DELETE }
    final Set<Authority> authorities;

    // 재귀적 타입 파라미터를 가진 제너릭 타입
    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Authority> authorities = EnumSet.noneOf(Authority.class);
        // 서브타입에서 권한을 정의할 수 있음
        public T addAuthorities(Authority types) {
            authorities.add(Objects.requireNonNull(types));
            return self();
        }

        abstract Member build();

        // 하위 클래스는 반드시 자기 자신을 반환하는 메서드를 오버라이드 해야 한다.
        protected abstract T self();
    }

    Member(Builder<?> builder) {
        authorities = builder.authorities.clone();
    }
}
public class Guest extends Member {
    private final String name;
    public static class Builder extends Member.Builder<Builder> {
        private final String name;

        // 1. Guest 객체가 가져야할 인스턴스 변수
        public Builder(String name) { this.name = Objects.requireNonNull(name); }

        // 2. 마지막으로 호출되는 빌드 완성 메서드
        // - 공변 반환 타이핑(covariant return typing)을 사용하면, 빌더를 사용하는 클라이언트는 캐스팅이 필요없다.
        @Override Guest build() { return new Guest(this); }

        // 3. 부모 타입에서 필요한 서브 타입의 참조
        @Override protected Builder self() { return this; }
    }
    private Guest(Builder builder) {
        super(builder);
        name = builder.name;
    }
}

// 상위 타입의 Members에 정의된 Authorities를 원하는 대로 추가할 수 있다.
Guest guest = new Guest.Builder("myguests")
    .addAuthorities(Member.Authority.READ)
    .addAuthorities(Member.Authority.WRITE).build();
  • 가변 인수 매개변수를 여러개 사용할 수 있다.
  • 공변 반환 타이핑(covariant return typing)
    • 클라이언트가 형변환에 신경 쓰지 않고도 빌더를 사용할 수 있다.
    • 하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 하위 타입(Guest)을 반환한다.
  • 코드 출처

단점

  • 객체를 만들기 위해, 빌더부터 생성해야 하므로 성능에 민감한 상황에서는 문제가 될 수 있다.
  • 매개변수가 4개 이상은 되어야 빌더 패턴의 가치가 있다.

Leave a comment