본문으로 바로가기

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

category JAVA 2022. 7. 24. 14:22

#이펙티브 자바

정적 팩터리와 생성자에는 매개변수가 많을수록 적절히 대응하기 어렵다는 단점이 있다.

  1. 식품의 영양정보를 표기하는 클래스를 만들어보자.

    • 영양정보는 1회내용량, 총n회제공량, 1회제공량당 칼로리 같은 필수항목과 총 지방 트랜스지방, 포화지방, 콜레스테롤, 나트륨 등 총 20개가 넘는 항목으로 이루어진다.
    • 이런 클래스의 생성자 혹은 정적 팩터리는 어떤 모습일까?
    • 점층적 생성자 패턴을 주로 사용한다.
    // 점층적 생성자 패턴기법
    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 NutritionFacts(int servingSize, int servings){
            this(servingSize, servings, 10);
        }
    
        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);
        }
    
        public NutritionFacts(int servingSize, int servings, int calories,
                              int fat, int sodium){
            this(servingSize, servings, calories, fat, sodium, 0);
        }
    
        public NutritionFacts(int servingSize, int servings, int calories,
                              int fat, int sodium, int carbohydrate){
            this.servingSize = servingSize;
            this.servings = servings;
            this.calories = calories;
            this.fat = fat;
            this.sodium = sodium;
            this.carbohydrate = carbohydrate;
        }
    }
    • 이러한 경우 원하는 객체를 만들기 위해 원하는 매개변수를 포함하는 생성자를 골라 호출하면 된다.
    • 하지만 매개변수가 늘어나게 되면 금세 걷잡을 수 없게 된다.
  2. 점층적 생성자 패턴의 대안

    • 자바빈즈 패턴
    class NutritionFacts{
    
        private int servingSize  = -1;  // 필수
        private int servings     = -1;  // 필수
        private int calories     = 0;   // 선택
        private int fat          = 0;   // 선택
        private int sodium       = 0;   // 선택
        private int carbohydrate = 0;   // 선택
    
        public NutritionFacts() { }
    
        public void setServingSize(int servingSize) {
            this.servingSize = servingSize;
        }
    
        public void setServings(int servings) {
            this.servings = servings;
        }
    
        public void setCalories(int calories) {
            this.calories = calories;
        }
    
        public void setFat(int fat) {
            this.fat = fat;
        }
    
        public void setSodium(int sodium) {
            this.sodium = sodium;
        }
    
        public void setCarbohydrate(int carbohydrate) {
            this.carbohydrate = carbohydrate;
        }
    }
    NutritionFacts cocaCola = new NutritionFacts();
    cocaCola.setServingSize(240);
    cocaCola.setServings(8);
    cocaCola.setCalories(100);
    cocaCola.setSodium(35);
    cocaCola.setCarbohydrate(27);
    • 자바빈즈 패턴에선 점층적 생성자 패턴의 문제가 보이지 않는다.
    • 하지만 심각한 단점이 있다.
      객체 하나를 만들기 위해 여러 메서드를 호출해야하고 객체가 완성되기 전까진 일관성이
      유지되지 못하게 된다. 이러한 문제로 자바빈즈 패턴에선 불변클래스를 얻을 수 없게 된다.
  3. 빌더 패턴

    • 필요한 객체를 직접 만드는 대신 필수 매개변수만으로 생성자를 호출하여 빌더 객체를 얻은 후에 원하는 선택매개변수들을 세터 메서드를 이용하여 설정하고 마지막으로 매개변수가 없는 build()메서드로 원하는 객체를 얻는다.
    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 calories){
                this.calories = calories;
                return this;
            }
    
            public Builder fat(int fat){
                this.fat = fat;
                return this;
            }
    
            public Builder sodium(int sodium){
                this.sodium = sodium;
                return this;
            }
    
            public Builder carbohydrate(int carbohydrate){
                this.carbohydrate = carbohydrate;
                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;
            sodium       = builder.sodium;
            carbohydrate = builder.carbohydrate;
        }
    }
    • 빌더패턴을 사용하면 NutritionFacts 클래스는 불변객체로 활용할수 있으며, 코드를 쓰기 쉽게할 수 있으며, 읽기도 쉬워진다.

필자도 현업에 종사하면서 매개변수가 많은 생성자들을 많이 접해봤고 작성도 해보았다.
이러한 코드를 작성할때마다 느꼈지만 복잡하고 난해하기 그지없었다. 매개변수의 순서와 타입까지 맞춰줘야 하니까 말이다. 잘못된 매개변수값을 던지면 런타임시점에서 잡아주지도 못하기 때문에 항상 주된 에러를 일으키는 주범이기도 한데 빌더 패턴을 이용하면 이러한 문제들이 해결될 것으로 본다.