정적 팩토리와 생성자는 선택적 매개변수가 많을 때 적절히 대응하기 어렵다는 제약이 있다.
점층적 생성자 패턴
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, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 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;
}
}
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
점층적 생성자 패턴은 사용자가 설정하길 원치 않는 매개변수에도 값을 지정해줘야 되며 매개변수가 많아지면 코드를 작성하거나 읽기 어렵다는 단점이 있다.
자바빈즈 패턴
매개변수가 없는 기본 생성자로 객체를 만든 후, setter 메서드로 원하는 매개변수의 값을 설정하는 방식이다.
public 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);
자바빈즈 패턴은 객체 하나를 만들기 위해 여러 메서드를 호출해야 하며, 객체가 완전히 생성되기 전에는 일관성(consistency)이 무너진다. 원하는 객체값을 세팅하기 전에 해당 객체를 가져다 사용할 수 있는 것이다.
따라서 객체가 생성된 이후에 필드 값을 수정하기 때문에 클래스를 불변(final)으로 만들 수 없다.
빌더 패턴
빌더 패턴은 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻은 후 해당 객체가 제공하는 일종의 setter 메서드로 원하는 선택 매개변수들을 설정한다. 마지막으로 build 메서드를 호출해 객체를 얻는다.
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 = 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 calories) {
this.sodium = sodium;
return this;
}
public Builder carbohydrate(int calories) {
this.carbohydrate = carbohydrate;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
this.servingSize = builder.servingSize;
this.servings = builder.servings;
this.calories = builder.calories;
this.fat = builder.fat;
this.sodium = builder.sodium;
this.carbohydrate = builder.carbohydrate;
}
}
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).build();
이를 통해 점층적 생성자보다 코드 가독성이 훨씬 좋고 자바빈즈 패턴보다 일관성이 있기 때문에 훨씬 안전하다.
또 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기 좋다.
부모 클래스에 빌더를 정의하고 하위 클래스의 빌더는 해당하는 구체 하위 클래스를 반환하도록 선언하면 클라이언트가 형변환에 신경 쓰지 않고 빌더를 사용할 수 있다.
하위 클래스의 메서드가 상위 클래스의 메서드가 정의한 반환 타입이 아닌, 그 하위 타입을 반환하는 기능을 공변 반환 타이핑(covariant return typing)이라 한다.
'Study > 이펙티브 자바' 카테고리의 다른 글
[effectice java] item 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2023.09.26 |
---|---|
[effectice java] item 3. private 생성자나 열거 타입으로 싱글톤임을 보장하라 (0) | 2023.09.24 |
[effectice java] item 1. 생성자 대신 정적 팩토리 메소드를 고려하라 (0) | 2023.09.07 |