- 객체 생성 방법 중 static factory method 와 constructor 를 통한 객체 생성 방법은 선택적 매개변수가 많을 때, 대응이 어렵다.
- 점층적 생성자 패턴(Telescoping constructor pattern)
여러개의 생성자를 만들어서, 필요한 매개변수 조합을 선택하여 객체를 만드는 방식을 점층적 생성자 패턴이라고 한다. 위 방식은 매개변수 개수가 많아질수록 가독성이 매우 떨어진다.
예를 들면 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public Class NutritionFacts{ private final int serviceSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate;
public NutritionFacts(int serviceSize, int servings){ this(servingSize,servings,0); } public NutritionFacts(int serviceSize, int servings,int calories){ this(servingSize,servings,calories,0); } public NutritionFacts(int serviceSize, int servings,int calories, int fat){ this(servingSize,servings,calories,fat,0); } public NutritionFacts(int serviceSize, int servings,int calories, int fat, int sodium , int carbohydrates ){ this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; }
}
|
- 자바빈즈 패턴(JavaBeans pattern)
매개변수가 없는 생성자로 객체를 우선 만든 뒤에,setProperty1,2…. 방식으로 원하는 매개변수 값을 설정하는 방식이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class NutritionFacts {
private int serviceSize = -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 setServiceSize(int serviceSize) { this.serviceSize = serviceSize; } 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; } public static void main(String[] args) { NutritionFacts nutritionFacts = new NutritionFacts(); nutritionFacts.setServings(120); nutritionFacts.setFat(20); } }
|
점층적 생성자 패턴 방식에 비해 확실히 코드의 가독성은 증가하였다.
하지만 객체 하나를 만드려면 여러 메소드를 호출해야하고,
중간중간에 사용자가 원하지 않는 상태의 객체가 생긴다는 단점이 있다.
이를 책에서는 “일관성이 무너진 상태”로 불변객체로 만들수가 없다고 한다.
단점을 완화하고자 객체 생성이 끝난 뒤에 freeze 메소드를 통해 객체를 수동으로 변경이 불가능하도록 만들 수 있다.
- 빌더패턴(Builder Pattern)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class NutritionFacts {
private final int serviceSize; 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 serving;
private int calories = 0; private int fat = 0; private int sodium =0; private int carbohydrate =0;
public Builder(int servingSize, int serving) { this.servingSize = servingSize; this.serving = serving; }
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); } }
public NutritionFacts(Builder builder) { serviceSize = builder.servingSize; servings = builder.serving; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } }
|
정리를 하면, 생성할 class의 static class로 builder를 만들고, builder 안에서 property 를 설정할 수 있는 메소드를 정의하고, 메소드의 반환값을 builder로 하여,
method들이 연쇄적으로 호출될 수 있게한다.
이를 메소드 체이닝 또는 Fluent API 라고 부른다. 어쩄거나 위의 방식을 통해 생성한 객체는 다음과 같이 만들 수 있다.
1 2 3 4 5 6
| public static void main(String[] args) { NutritionFacts nutritionFacts = new Builder(280, 240) .calories(100) .sodium(35) .build(); }
|
빌더 패턴의 장점은 가독성이 뛰어나고, 불변객체를 만들수 있다.
또한 계층적 구조에도 활용 가능하여, 확장이 용이하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public abstract class Pizza {
public enum Topping {HAM,ONION,PEPPER}; 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(); protected abstract T self(); }
Pizza(Builder<?> builder){ toppings = builder.toppings.clone(); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class MyPizza 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 Pizza build() { return new MyPizza(this); }
@Override protected Builder self() { return this; } }
MyPizza(Builder builder) { super(builder); size = builder.size; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class Calzone extends Pizza{
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder>{
private boolean sauceInside = false;
public Builder sauceInside(){ sauceInside = true; return this; } @Override Pizza build() { return new Calzone(this); }
@Override protected Builder self() { return this; } }
Calzone(Builder builder){ super(builder); sauceInside = builder.sauceInside; } }
|
1 2 3 4 5 6 7 8
| public static void main(String[] args) {
Pizza pz1 = new MyPizza.Builder(MyPizza.Size.SMALL) .addTopping(Topping.ONION).build();
Pizza pz2 = new Calzone.Builder() .addTopping(Topping.HAM).sauceInside().build(); }
|
- 정리 : 생성자나 정적 팩토리 메소드가 처리해야할 매개변수가 많다면 빌더패턴을 선택하는게 코드 가독성 측면과 불변객체를 안전하게 만들 수 있다는 측면에서 더 바람직하다.