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

객체 생성시 빌더패턴을 사용하자

Posted on 2019-05-06

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


정적 팩터리와 생성자에는 똑같은 제약이 하나 있다. 선택적 매개변수가 많을때 적절히 대응하기 어렵다는 점이다.

점층적 생성자 패턴

매개변수를 모두 포함하는 생성자들을 만들어 객체를 생성하는 방식이다.

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    
    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 = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
    }
}

NutritionFacts cola = new NutritionFacts(240, 8, 100, 10);

매개변수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.

  • 코드를 읽을 때 각 값의 의미가 무엇인지 헷갈릴 것이고, 매개변수가 몇개인지 주의해서 세어 보아야 할 것이다.
  • 타입이 같은 매개변수가 연달아 있는 경우, 클라이언트가 실수로 매개변수의 순서를 바꿔 건네줘도 컴파일러는 알아채지 못하고 결국 런타임에 엉뚱한 동작을 하게 된다.

자바빈즈 패턴

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

@Setter
public class NutritionFacts {
    private int servingSize = -1;
    private int servings = -1;
    private int calories = 0;
    private int fat = 0;
    
    public NutritionFacts() { }

}

NutritionFacts cola = new NutritionFacts();
cola.setServingSize(240);
cola.setServings(8);
cola.setCalories(100);
cola.setFat(10);

객체 하나를 만들려면 메서드를 여러개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.

  • 일관성이 깨진 객체는 런타임에 문제를 겪는 코드가 물리적으로 멀리 떨어져 있을 것이므로 디버깅도 만만치 않다.
  • 클래스를 불변으로 만들 수 없다.

빌더 패턴

필수 매개변수로 이루어진 빌더 클래스 생성자에 선택 매개변수를 추가하여 생성하는 방식이다.

@Setter
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    
    public static class Builder { 
        // 필수  
        private final int servingSize;
        private final int servings;
        // 선택
        private int calories = 0;
        private int fat = 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 NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
    
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
    }

}

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

  • 클래스는 불변이며, 모든 매개변수의 기본값들을 한 곳에 모아뒀다.
  • 빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다. 이런 방식을 fluent API 혹은 Method Chaining 이라 한다.
  • 클라이언트 코드는 쓰기 쉽고, 무엇보다 읽기 쉽다.

불변(immutable)은 어떠한 변경도 허용하지 않는다는 뜻으로, 주로 변경을 허용하는 가변(mutable) 객체와 구분하는 용도로 쓰인다.


핵심 정리

생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는게 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 더 그렇다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.


Reference
  • Effective Java