생성자나 정적 팩터리 메서드 둘 다 선택적 파라미터가 많은 경우에 문제가 있다.

 

1. 점층적 생성자 패턴


영양 성분표를 나타내는 클래스를 예로 들어보자.

public class NutritionFacts {
    private final int servingSize;  // 필수로 입력되어야하는 파라미터
    private final int servings;     // 필수
    private final int calories;     // 선택
    private final int fat;          // 선택
    private final int protein;      // 선택
    private final int carbohydrate  // 선택
    
    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }
    
    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0);
    }
    
    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }
    
    // 위의 패턴대로 파라미터 갯수에 따라 모든 생성자를 만들어야 한다.
}

 

위와 같은 패턴을 점층적 생성자 패턴이라 하는데, 파라미터 갯수별로 생성자를 모두 만들어야 한다. 당연히 코드는 잘 동작할테지만 파라미터가 늘어나면 늘어날수록 가독성이 떨어지게 된다.

 

2. 자바빈 패턴


생성자에 전달되는 파라미터 갯수가 많을 때 적용 가능한 두 번째 대안은 자바빈 패턴이다.

public class NutritionFacts {
    private final int servingSize;  // 필수로 입력되어야하는 파라미터
    private final int servings;     // 필수
    private final int calories;     // 선택
    private final int fat;          // 선택
    private final int protein;      // 선택
    private final int carbohydrate  // 선택
    
    public NutritionFacts() {}
    
    public void setServingSize(int val) { servingSize = val; }
    public void setServings(int val) { servings = val; }
    public void setCalories(int val) { calories = val; }
    public void setFat(int val) { fat = val; }
    public void setProtein(int val) { protein = val; }
    public void setCarbohydrate(int val) { carbohydrate = val; }
}

 

이 패턴은 점층적 생성자 패턴보다 객체를 생성하기도 쉽고, 가독성도 좋다. 하지만, 자바빈 패턴에는 심각한 단점이 있는데, 1회의 함수 호출로 객체 생성을 끝낼 수 없으므로 객체 일관성이 일시적으로 깨질 수 있다는 것이다. 생성자의 인자가 유효한지 검사하여 일관성을 보장하는 단순한 방법을 여기서는 사용할 수 없다. 이와 관련된 또 다른 문제는 자바빈 패턴으로는 변경 불가능(immutable) 클래스를 만들 수 없다는 것이다. 스레드 안전성(thread-safety)을 제공하기 위해 해야할 일도 더 많아진다.

 

3. 빌더 패턴


Builder라는 내부 객체를 이용하는 방법으로 코드를 살펴보자.

public class NutritionFacts {
    private final int servingSize;  // 필수로 입력되어야하는 파라미터
    private final int servings;     // 필수
    private final int calories;     // 선택
    private final int fat;          // 선택
    private final int protein;      // 선택
    private final int carbohydrate  // 선택
    
    public static class Builder {
        private final int servingSize;      // 필수로 입력되어야하는 파라미터
        private final int servings;         // 필수
        private final int calories = 0;     // 선택
        private final int fat = 0;          // 선택
        private final int protein = 0;      // 선택
        private final 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 protein(int val) { protein = val; return this; }
        public Builder carbohydrate(int val) { carbohydrate = val; return this; }
        
        public NutritionFacts build() { return new NutritionFacts(this); }
    }
    
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        protein = builder.protein;
        carbohydrate = builder.carbohydrate;
    }
}

 

이어서 객체를 생성하는 코드를 살펴보자.

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                                            .calories(100)
                                            .fat(10)
                                            .carbohydrate(27)
                                            .build();

 

위와 같은 방법은 작성하기도 쉽고 가독성도 좋다.