아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라
- 정적 팩터리와 생성자에 선택적 매개변수가 많을 때
1. 점층적 생성자 패턴 (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 fianl 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);
}
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.soduim= sodium;
this.carbohydrate= carbohydrate;
}
}
- 사용자가 설정하길 원치 않는 매개변수까지 값을 지정해줘야한다.
- 점층적 생성자 패턴도 사용할 수 있으나, 매개변수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.
2. 자바빈즈 (JavaBeans pattern)
- 매개벼수가 없는 생성자로 객체를 만든 후, 세터메서드를 호출해 매개변수의 값을 설정하는 방식
// 자바빈즈 패턴 : 일관성이 깨지고, 불변으로 만들 수 없다.
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) { sodium= val;}
public void setCarbohydrate(int val) { calgohydrate = val;}
}
// 자바빈즈 패턴에서 객체 생성시
NutritionFacts cocaCola= new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
- 객체 하나를 만들기 위해서 여러 개의 메서드를 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.
- 자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없으며, 스레드 안정성을 얻으려면 프로그래머가 추가 작업을 해줘야만 한다.
3. 빌더패턴(Builder pattern) : 점층적 생성자 패턴의 안정성 + 자바빈즈 패턴의 가독성
- 클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다.
- 그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 매개변수를 설정한다.
- 마지막으로 매개변수가 없는 build메서드를 호출해 우리에게 필요한 객체를 얻는다.
// 빌즈패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다.
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 fianl int sodium; // (mg/1회 제공량) 선택
private final int carbohydrate; //(g/1회 제공량) 선택
// 빌더는 생성할 클래스 안에 정적 멤버 클래스로 만들어두는게 보통이다.
public static class Builder{
//필수 매개변수
private final int servingSize; // (ml, 1회 제공량) 필수
private final int servings; // (회, 총 n회 제공량) 필수
//선택 매개변수 - 기본값으로 초기화 한다.
private final int calories = 0; // (1회 제공량당) 선택
private final int fat= 0; // (g/1회 제공량) 선택
private fianl int sodium= 0; // (mg/1회 제공량) 선택
private final int carbohydrate= 0; //(g/1회 제공량) 선택
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;
soduim= builder.sodium;
carbohydrate= builder.carbohydrate;
}
}
- 해당 클래스는 불변이며, 모든 매개변수의 기본값들을 한곳에 모아뒀다.
- 빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다 : 플루언트API 또는 메서드연쇄 라 한다.
NutritionFacts cocaCola= new NutritionFacts.Builder(240,8).calories(100).sodium(35).carbohydrate(27).build();
- 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
- 하위클래스 메서드는 상위클래스 메서드가 정의한 반환 타입이 아닌, 그 하위 타입을 반환하는 기능을 사용할 수 있다 : 공변 반환 타이핑 기능
- 빌더 패턴은 상당히 유연해서, 하나의 빌더로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수도 있다.
빌더 패턴의 단점
- 객체를 만들려면, 빌더부터 만들어야 한다.
- 생성 비용이 크지는 않지만, 성능이 민감한 상황에서는 문제가 될 수 있다.
- 매개변수가 4개 이상은 되어야 값어치를 한다.
정리
생성자나 정적팩터리가 처리해야할 매개변수가 많다면(4개이상) 빌더 패턴을 선택하는게 더 낫다. 매개변수 중 다수가 필수가 아니라 같은 타입이면 특히 더 그렇다.
빌더는 점층적생성자 보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.
'JAVA > Effective Java' 카테고리의 다른 글
아이템6. 불필요한 객체 생성을 피하라. (0) | 2022.05.21 |
---|---|
아이템5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라. (0) | 2022.05.21 |
아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2022.05.20 |
아이템3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2022.05.20 |
아이템1. 생성자 대신 정적팩터리 메서드를 고려하라. (0) | 2022.05.20 |