# ITEM2 생성자에 매개변수가 많다면 빌더를 고려하라

정적 팩터리와 생성자의 공통 제약

  • 선택적 매개변수가 많을 때 대응의 어려움

# 여러 선택적 매개변수의 몇가지 대응 방법들

# 점층적 생성자 패턴 telescoping constructor pattern

확장하기 어려운 특징

public class NutritionFacts {
    private final int servingSize;  // required
    private final int servings;     // required
    private final int calories;     // options         
    private final int fat;          // options
    private final int sodium;       // options
    private final int carbohydrate; // options
    
    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;
    }

}
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

단점

  1. 사용자가 설정하기 원치않은 매개변수까지 값을 지정해줘야 한다.
  2. 매개 변수가 많아지만 클라이언트 코드를 작성하거나 읽기 어렵다.
  3. 클라이언트가 매개변수의 순서를 바꿔 건네줘도, 런타임에 엉뚱한 동작을 하게 됨[51]

# 자바빈즈 패턴 JavaBeans Pattern

매개 변수가 없는 생성자로 객체를 만든 후, setter 메서드 호출하여 원하는 값을 설정

public class NutritionFacts {
    private int servingSize = -1;   // required 기본값 없음
    private int servings = -1;      // required 기본값 없음
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;
    
    public NutritionFacts() {}
    
    public void setServingSize(int val) { servingSize = val; }
    public void setServings(int val) {servings = val; }
    // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

...

class SomeWhere {
    NutritionFacts cocaCola = new NutritionFacts();
    cocaCola.setServingSize(240);
    cocaCola.setServings(8);
    // ...
}
1
2
3
4
5
6

단점

  1. 객체 하나를 생성하기 위해 메서드를 여러개 호출해야 함
  2. 객체 완성 전까지 일관성 consistency 가 무너진 상태
    • 런타임, 디버깅 문제
  3. 클래스를 불변으로 만들 수 없다.
    • 스레드 안정성을 얻으려면 추가 작업 필요

# 빌더 패턴 Builder pattern

점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다.

public class NutritionFacts {
    // final 로 선언 -> 불변
    private final int servingSize;
    private final int servings;
    private final int calories; 
    // ...
    
    public static class Builder {
        // 필수 매개 변수
        private final int servingSize;
        private final int servings;
    
        // 선택 매개 변수 - 기본값으로 초기화
        private int calories = 0;
        // ...
        
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
    
        public Builder calories(int val) {
            this.calories = val;
            // 연쇄적 호출 : fluent API, method chaining
            return this;
        }
        // ...
        
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        // ...
    }
}

class Client {
    NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                                                .calories(100)
                                                // ...
                                                .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
32
33
34
35
36
37
38
39
40
41
42
43
44

# 빌터 패턴의 특징

  1. 클라이언트가 쓰기/읽기 가 수월함
  2. 각각의 체이닝 메서드에서 유효성 검사 코드를 삽입할 수 있다.
  3. 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 한다.

# 계층적으로 설계된 클래스와 함께 쓰기 좋다.

public abstract class Pizza {
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
    final Set<Topping> toppings;

    abstract static class Builder<T extends Builder<T>> { // [30]
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
        public T addTopping(Topping topping) {
            toppings.add(Object.requiredNonNull(topping));
            return self;
        }

        abstract Pizza build(); 
    
        // 하위 클래스는 이 메서드를 override 하여 this 를 반환하도록 해야 한다. 
        // 하위 클래스에서 형 변환을 하지 않고도 메서드 연쇄 지원.
        protected abstract T self();

    }
    
    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone(); // [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.requiredNonNull(size);
        }

        // 구체 하위 클래스를 반환하도록 선언 -> 클라이언트가 형 변환에 신경쓰지 않고도 빌더 사용가능
        @Override public NyPizza build() {
            return new NyPizza(this);
        }
    
        @Override protected Builder self() {
            return this;
        }   
    }

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }
}

class Client {
    NyPizza pizza = new NyPizza.Builder(NyPizza.Size.SMALL)
                                .addTopping(Pizza.Topping.SAUSAGE)
                                .addTopping(Pizza.Topping.ONION)
                                .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
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