아이템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();
핵심 정리
생성자나 정적 팩토리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 것이 더 낫다.
빌더는 점층적 생성자 보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.