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

본글은 이펙티브자바 자바3의 내용을 기반으로 작성하였습니다.

Product.class

1
2
3
4
5
6
7
8
9
10

private int productId;
private String name;
private int price;
private int size;
private String tag;
private boolean isSale;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;

Product 클래스의 인스턴스를 만들기 위해 원하는 매개변수를 포함한 생성자를 골라 호출이 가능하다! But “매개변수의 갯수가 많아지게 될경우 클라이언트 코드를 작성하거나 읽기가 힘들어진다.”

Product.class with builder

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
66
67
68
69
70
71
72
73
74
75
76
77

public class Product {


private int productId;
private String name;
private int price;
private int size;
private int tag;
private boolean isSale;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;



public static class Builder{

//필수 매개 변수
private final int productId;
private final String name;


//선택 매개변수 - 기본값을 초기화
private int price = 0;
private int size = 0;
private int tag = 0;
private boolean isSale = false;


public Builder(int productId, String name) {
this.productId = productId;
this.name = name;
}

public Builder price(int value){
price = value; return this;
}

public Builder size(int value){
size = value; return this;
}
public Builder tag(int value){
tag = value; return this;
}
public Builder isSale(boolean value){
isSale = value; return this;
}

public Product build(){
return new Product(this);
}

}


public Product(Builder builder) {
this.productId = builder.productId;
this.name = builder.name;
this.price = builder.price;
this.size = builder.size;
this.tag = builder.tag;
this.isSale = builder.isSale;
}

@Override
public String toString() {
return "Product{" +
"productId=" + productId +
", name='" + name + '\'' +
", price=" + price +
", size=" + size +
", tag=" + tag +
", isSale=" + isSale +
'}';
}
}

Main.class

1
2
3
4
5
6
7
8
9
10
11
12
13

public static void main(String[] args) {

Product product = new Product.Builder(35,"sampleProduct")
.isSale(true).price(1350000).size(35).tag(365).build();


System.out.println(product.toString());
}



result -> Product{productId=35, name='sampleProduct', price=1350000, size=35, tag=365, isSale=true}

위와 같이 빌더를 활용하여 생성자를 만들수 있다. 모든 매개변수의 기본값들을 한곳에 모을수 있으며 빌더의 세터메서드들은 빌더자신을 반환하기 때문에 연쇄적인 호출이 가능하다.

빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기 좋다.

추상클래스는 추상 빌더를, 구체 클래스는 구체 빌더를 갖게 한다.

ex) 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴

HotDog.class

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
public abstract class HotDog {
public enum Topping { HAM, ONION, GALIC, SAUSAGE, BULGOGI }
Set<Topping> toppingList;

abstract static class Builder<T extends Builder<T>>{

EnumSet<Topping> toppingList = EnumSet.noneOf(Topping.class);

public T addTopping(Topping topping){
toppingList.add(Objects.requireNonNull(topping));
return self();

}

abstract HotDog build();


protected abstract T self();

}


HotDog(Builder<?> builder){
toppingList = builder.toppingList.clone();
}
}

BeanBrokerHotDog.class

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 BeanBrokerHotDog extends HotDog {

public enum Bread { SOFT_BREAD, NORMAL_BREAD, HARD_BREAD}
private final Bread bread;

public static class Builder extends HotDog.Builder<Builder>{


private final Bread bread;

public Builder(Bread bread) {
this.bread = Objects.requireNonNull(bread);
}


@Override
public BeanBrokerHotDog build() {
return new BeanBrokerHotDog(this);
}

@Override
protected Builder self() {
return this;
}
}

private BeanBrokerHotDog(Builder builder) {
super(builder);
bread = builder.bread;
}


}

각 하위 클래스의 빌더가 정희한 build 메서드는 해당하는 구체 하위 클래스를 반환하도록 선언한다. BeanBrokerHotDog.Builder는 BeanBrokerHotDog를 반환한다.

빌더 패턴은 상당히 유연하며 빌더 하나로 여러 객체를 순회하면서 만들수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수 있다. 객체마다 부여되는 일련번호 같은 특정 필드는 빌더가 알아서 채우도록 할수도 있다.

But 빌더 패턴에 장정만 있는 것은 아니다. 객체를 만들려면 그에 앞서 빌더부터 만들어야 한다. 빌더 생성 비용이 크지는 않지만.. 성능에 민감한 상황에서는 문제가 될수 있따.

핵심 - 생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 것이 낫다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고 자바빈즈보다 훨씬 안전하다.