티스토리 뷰

정적팩토리나 생성자에는 똑같은 제약이 있다.

→ 둘 다 선택적 매개변수가 많을때 적절히 대응이 어렵다는 점

 

이렇게 선택적 매개변수가 많을때는 어떻게 효율적으로 사용할 수 있을까 ?

  • 점층적 생성자 패턴
  • 자바빈즈 패턴
  • 빌더 패턴

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

필수 매개변수만 받는 생성자 → 필수 매개변수, 선택 매개변수1 ... 필수 매개변수, 선택 매개변수 n ... 이렇게 전부 다 받는 생성자까지 늘려가는 방식

→ 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드 작성이나 읽기가 어렵다.

→ 각 값의 의미가 헷갈림.

→ 타입이 같은 매개변수가 연달아 있으면 찾기 어려움.

→ 결국 런타임에 엉뚱한 동작 발생.

 

자바빈즈 패턴 (JavaBeans pattern)

매개변수가 없는 생성자로 객체를 만든 후, setter 메서드를 호출해 원하는 매개변수의 값을 설정하는 방식

→ 코드가 길어지지만 인스턴스 생성이 쉬움.

→ 코드 가독성이 좋음.

→ 객체 하나를 만들기 위해선 setter 메소드 호출이 너무 잦을 수 있음.

→ 객체가 완전히 생성되기 까지는 일관성 (consistency)가 무너진 상태임.

→ 클래스를 불변으로 만들수 없음.

→ 스레드 안전성을 위해서는 추가작업이 필요함.

 

빌더 패턴 (Builder pattern)

점층적 생성자 패턴의 안전성 + 자바빈즈 패턴의 가독성 ⇒ 빌더 패턴

클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(혹은 정적 팩토리)를 호출해 빌더 객체를 얻는다.

→ 쓰기 쉽고, 읽기 쉬움.

→ 유연하다. (빌더 하나로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수 도 있다.)

→ 코드가 장황해 매개변수가 4개 이상은 되어야 값어치를 한다. (API 는 시간이 지날수록 매개변수가 많아지는 경향이 있다.)

→ 객체를 만들기 이전에 빌더 부터 만들어야 한다. (빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수 있음.)

→ 생성자나 정적팩토리 방식으로 시작했다가 추후에 매개변수가 많아지면 빌더 패턴으로 바꿀수도 있지만, 애초에 빌더로 시작하는 편이 권장된다.

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // 필수 매개변수
        private final int servingSize;
        private final int servings;

        // 선택 매개변수 - 기본값으로 초기화한다.
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    public NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

// 클라이언트 코드
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                .calories(100)
                .sodium(35)
                .carbohydrate(27)
                .build();