본문 바로가기

디자인 패턴

빌더 패턴

아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라 by Effective Java

Q. 프로그래머들은 선택적 매개 변수가 많을 때 어떤 방식으로 생성자를 만들까..?

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

프로그래머들은 선택적 매개변수가 많을 때 점층적 생성자 패턴(telescoping constructor pattern)을 즐겨 사용 했다.
하지만 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.

public class NutritionFacts {
    private final int servingSize; // (ml, 1회 제공량) 필수
    private final int servings; //(회,총n회제공량) 필수
    private final int calories; // (1회 제공량당) 선택 
    private final int fat; // (g/1회 제공량) 선택
    private final int sodium; // (mg/1회 제공량) 선택
    private final int carbohydrate; // (g/1회 제공량) 선택
    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); 
    }
    public NutritionFacts(int servingSize, int servings, int calories, 
                          int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0); 
    }
}

위의 점층적 생성자 패턴의 예시 코드보면 각 값의 의미가 무엇인지 헛갈리고, 매개변수가 몇개인지도 주의해서 세어 보아야 한다. 특히, 타입이 같은 매개 변수가 있으면 정말 찾기가 어렵고 의도 하지 않게 실수로 매개변수의 순서가 바뀔 경우 런타임 시 엉뚱한 동작을 하게 될 것이다.

2. 자바빈즈 패턴

자바빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성(consistency)이 없는 상태 이다.
즉, 클래스를 불변으로 만들 수 없으며 스레드 안정성을 얻으려면 프로그래머가 추가 작업을 해줘야 한다.

public class NutritionFacts {
    private  int servingSize = -1; // (ml, 1회 제공량) 필수, 기본 값 없음
    private  int servings = -1; //(회,총n회제공량) 필수, 기본 값 없음
    private  int calories = 0; // (1회 제공량당) 선택 
    private  int fat = 0; // (g/1회 제공량) 선택
    private  int sodium = 0; // (mg/1회 제공량) 선택
    private  int carbohydrate = 0; // (g/1회 제공량) 선택
    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 setSodium(int val) { sodium = val; }
    public void setCarbohydrate(int val)  { carbohydrate = val; }
}

점층적 생성자 패턴에서는 매개변수들이 유효한지를 생성자에서만 확인하면 일관성을 유지할 수 있었지만 자바빈즈의 심각한 단점은 객체 생성 후 세터 메서드를 통하여 값을 계속해서 변경할 수 있기 때문에 클래스를 불변으로 만들 수 없다.

3. 빌더 패턴

점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다.
빌더 패턴은 파이썬과 스칼라에 있는 명명된 선택적 매개변수를 흉내 낸 것이다.

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
        private int fat
        private int sodium
        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); 
        }
    }
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbonhydrate;
    }
}

빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적 호출이 가능하다.
점층적 생성자 패턴의 안전성과 자바 빈즈 패턴의 가독성을 모두 충족하는 패턴이다.
아래는 빌더 클래스를 사용하는 클라이언트의 코드 이다.

NutritionFacts chilsungCider = new NutritionFacts.Builder(240, 8)
                                                    .calories(100)
                                                    .sodium(40)
                                                    .carbohydrate(20)
                                                    .build();

핵심 정리

생성자나 정적 팩토리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 것이 더 낫다.
빌더는 점층적 생성자 보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.