본문 바로가기
Java

Effective Java #02 생성자 인자가 많을 때는 Builder 패턴 적용을 고려하라.

by NaHyungMin 2016. 4. 12.

책에 나온 예문을 추려보면 다음과 같다.


1회 제공 칼로리에 지방, 포화 지방, 트랜스 지방, 콜레스테롤, 나트륨 등이 있는데 이런 클래스에는 어떠한 생성자나 정적 팩터리 메서드가 적합할까?


보통 프로그래머들은 이런 상황에는 다음과 같은 점층적 생성자 패턴 패턴을 적용한다고 한다.

점층적 생성자 패턴은 다음과 같다.


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
public class NutritionFacts{
    //자바에서 final은 변수면 C#으로는 readonly이며
    //함수에 final을 사용할 경우 상속 시, override를 사용할 시 오류가 난다.
    private final int servingSize; //(mL) 필수
    private final int servings;       //(per container) 필수
    private final int calories;    //이하 선택
    private final int fat;
    private final int sodium;
    private final int carbohydrate;
    
    private static final int zero = 0//책에는 없지만, 0이라는 magic number 대신 zero 상수 사용
 
    //책에는 매개변수가 적은 순으로 코딩되었지만, 필자의 경우는 범용인 생성자를 처음으로 둔다.
    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;
    }
 
    public NutritionFacts(int servingSize, int servings){
        this(servingSize, servings, zero , zero , zero , zero );
    }
 
    public NutritionFacts(int servingSize, int servings, int calories){
        this(servingSize, servings, calories, zero , zero , zero );
    }
 
    public NutritionFacts(int servingSize, int servings, int calories, int fat){
        this(servingSize, servings, calories, fat, zero , zero );
    }
 
    public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium){
        this(servingSize, servings, calories, fat, sodium, zero );
    }
}
cs

약간 책과는 다른 코드지만 비슷하다.
이 클래스로 객체를 생성할 때는 다음과 같이 골라 사용하면 된다.

NutritionFacts cocaCola = new NutritionFacts(240, 8);
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100);
......
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 3, 35, 27);


현재 5개의 매개변수까지는 괜찮지만, 코드 컴플리트나 다른 일반 서적에서도 주연 배우가 7명이상이거나 할 경우 사람의 인지가

줄어든다고 한다.

이 책에서도 마찬가지로 7개의 매개변수가 넘게 될 시, 문제가 생긴다는 식으로 이야기를 하고 있다.


이 문제점을 해결하기 위한 다음 방법은 자바빈 패턴이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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(){
    }
 
    //접근 설정자 setter
    public void setServingSize(int value) { this.servingSize = value };
    public void setServings(int value) { this.servings = value };
    public void setCalories(int value) { this.calories = value };
    public void setFat(int value) { this.fat = value };
    public void setSodium(int value) { this.sodium = value };
    public void setCarbohydrate(int value) { this.carbohydrate = value };
}
cs


자바빈 패턴의 사용법은 다음과 같다.

NutritionFacts cocaCola = new NutritionFacts();

cocaCola.setServingSize(240);

cocaCola.setServings(8);

cocaCola.setCalories(100);

cocaCola.setSodium(35);

cocaCola.setCarbohydrate(27);


하지만 이 경우 단점은 1회로 객체 생성을 끝낼 수 없으므로 객체 일관성이 일시적으로 깨진다는 것이다.

게다가 변경 불가능(immutable)클래스를 생성할 수가 없다고 한다. 이 부분에 대해선 잘 모르겠다. ^^ 

그 외 스레드 안정성을 제공하기 위해서는 할 일이 더 많아진다고 한다.


결과적으로 이 책에서 다음 대안으로 내세운 것이 빌더(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
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 carbohydrate = 0;
        private int sodium = 0;
 
        public Builder(int servingSize, int servings){
            this.servingSize = servingSize;
            this.srvings = servings;
        }
 
        public Builder calories(int value){
            this.calories = value;
            return this;
        }
 
        public Builder fat(int value){
            this.fat = value;
            return this;            
        }
 
        public Builder carbohydrate(int value){
            this.carbohydrate = value;
            return this;
        }
 
        public Builder carbohydrate(int value){
            this.sodium = value;
            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;
    }
}
cs

대략적인 코드는 다음과 같다.
이제 사용 코드를 보도록 하자
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(28).build();

코드를 작성하면서 느낀 것이지만, 자바에는 구조체가 없다. 코드 컴플리트를 배운 나로써는 매개변수가 7개가 넘으면 그때 구조체를 만들어서
매개변수로 구조체를 넘겨 사용하였다.

다음과 같은 빌더가 있으면 어떠한 자료형에도 사용할 수 있다고 한다.

//자료형이 T인 객체에 대한 빌더
public interface Builder<T>{
public T build();
}

이러한 장점이 있어도, 역시 변수 및 매개변수를 2배 이상 만들기 때문에 성능이 중요한 상황에서는 다른 방법을 사용하는게 좋다고 한다.
하지만 성능이 어느정도 된다는 점과 규모가 커질 수 있는 클래스에 경우 처음부터 빌더 패턴을 사용해도 좋을 것 같다.