점층적 생성자 패턴
영양정보를 표현하는 클래스가 있다. 이 클래스에는 필수항목인 "1회 제공량", "총 n회 제공량"과 선택항목인 "칼로리", "지방", "나트륨", "탄수화물" 이 필드로 존재한다. 이 클래스의 생성자는 6개의 매개변수가 필요하고 필수항목만 입력하고 싶어도 6개의 매개변수를 모두 보내주어야 한다. 이러한 비효율적인 상황을 해결하기 위해 점층적 생성자 패턴을 사용할 수 있다.
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, 0, 0, 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;
}
}
// 생성자로 전달되는 매개변수가 각각 어떤 의미인지 알기가 어렵다.
new NutritionFacts(10, 100);
new NutritionFacts(10, 100, 0, 0 ,0 ,0);
6개의 매개변수를 받는 생성자가 있고, 필수 값만을 매개변수로 받는 생성자를 점층적 생성자 패턴으로 만들었다. 하지만 이역시 문제가 존재한다. 선택 항목 중 일부만 매개변수로 받는 생성자를 만들려고 한다면, 수없이 많은 생성자를 만들어야 한다. 그리고 생성자에 전달되는 매개변수가 각각 어떤 의미인지 알기가 어렵다.
자바빈즈패턴
앞에서 말한 선택 항목 중 일부만 사용할 때의 문제와 코드의 의미를 파악하기 어려운 문제는 자바빈즈패턴을 사용하면 해결이 가능하다. 기본생성자로 인스턴스를 만들고 사용할 값만 set 메소드로 넣어서 사용하면 된다.
public class NutritionFacts {
private int servingSize;
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
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 nf = new NutritionFacts();
nf.setServingSize(10);
nf.setServings(100);
nf.setCalories(300);
하지만 이렇게 객채를 만들고 사용하면, 기본 생성자를 호출하고 값들을 넣기까지 해당 객체의 일관성이 무너지게된다. 얼마든지 값을 모두 넣기 전에 생성된 객체를 가져다 사용할 수 있다. 그리고 생성된 이후 필드에 값을 넣어야 하기 때문에, 필드를 불변(final)으로 만들 수 없다.
빌더 패턴
위의 문제들을 모두 해결하여 유연하게 필드를 채워 넣을 수 있으면서도 일관성을 잃지 않고, 코드의 의미를 파악할 수 있어서 가독성도 뛰어난 방법이 있다. 바로 빌더 패턴이다.
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(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;
}
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);
}
}
}
// 객체 생성 중 일관성이 깨지지 않고, 가독성 또한 좋다.
NutritionFacts coca = new NutritionFacts.Builder(10, 100).calories(300).build();
또 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
public abstract class Pizza {
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
// Subclasses must override this method to return "this"
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // See Item 50
}
}
public class NyPizza extends Pizza {
public enum Size { SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override public NyPizza build() {
return new NyPizza(this);
}
@Override protected Builder self() { return this; }
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
@Override public String toString() {
return "New York Pizza with " + toppings;
}
}
+ lombok
@Builder
public class MyClass{
...
}
// lombock을 사용하고 있다면 @Builder 어노테이션을 사용하여 자동으로 빌더패턴을 적용할 수 있다.
MyClass mc = MyClass.builder().build();
'STUDY > 이펙티브자바' 카테고리의 다른 글
2-6) 불필요한 객체 생성을 피하라 (0) | 2022.08.12 |
---|---|
2-5) 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2022.08.12 |
2-4) 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2022.08.12 |
2-3) private 생성자나 열거 타입(enum)으로 싱글톤을 보장하라 (2) | 2022.08.11 |
2-1) 생성자 대신 정적 팩토리 메소드를 고려하라. (0) | 2022.08.10 |