2019년 4월 21일 일요일

[Effective Java 2nd Edition] Chapter 2. Creating and Destroying Objects -> Item 2. 생성자 파라미터가 많을 때 Builder를 고려해보자.

d어떤 클래스의 생성자 파라미터가 많을 때 흔히 사용하는 패턴에 대해서 소개한다.

1. 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
public class NutritionFacts {
    private final int servingSize; //required
    private final int servings;    //required
    private final int calories;    //optional
    private final int fat;           //optional
    ....
    //필수 멤버만 매개변수로 받는 생성자
    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) {        
        this(servingSize, servings, calories, fat, 0);
    }
    ...
}
cs

생성자를 통해서 설정하려는 매개변수가 많아지면, 매개변수를 순서대로 넣었는지 확인해야만 한다. 

2. JavaBeans pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
public class NutririonFacts {
    private int servingSize = -1// required: no default value
    private int servings = -1;      // required: no default value
    private int calories = 0;      // optional
    private int fat      = 0;
    ...
    public NutritionFacts() {}
    
    //setters
    public void setServingSize(int val) { servingSize = val; }
    ...
}
cs

setter를 통해서 설정해줘야 하는 값이 50개라고 하면.. 참 난감하다. 뭐가 required인지 찾아내는 것도 힘들 것이다. 

다행이 telescoping constructor pattern의 안전성과 JavaBeans pattern의 가독성을 모두 가진 대안이 있다. Builder pattern! 

Builder pattern은 필요한 객체를 직접 만들기보다 필수 매개변수와 함께 생성자나 static factory를 호출해서 builder 객체를 얻는다. builder 객체의 setter-like method를 호출해서 optional parameter를 설정한다. 




//Builder Pattern
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 {
        //Required parameters
        private final int servingSize;
        private final int servings;

        //Optional parameters - initialized to default values
        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.servings    = servings;
        }
        
        public Builder calories(int val) {
            calories = val;
            return this;
        }
        public Builder fat(int val) {
            fat = val; 
            return this;
        }
        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }
        public Builder sodium(int val) {
            sodium = val;
            return this;
        }
        
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
    
    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8). calories(100).sodium(35).carbohydrate(27).build();

Builder Pattern을 사용하면 위와 같이 가독성 높고 안전하게 NutritionFacts 객체를 생성할 수 있다!

흔히 Abstract Factory class의 newInstance 메소드가 빌더 역할을 하기도 하는데, 항상 존재하지 않을지도 모르는 기본 생성자를 호출한다는 단점이 있다. (잘 이해가 안된다. 더 공부해야 하는 부분..)

Builder pattern의 단점은 항상 Builder 객체를 먼저 생성해야 한다는 점이다. 어떤 상황에서는 성능 문제를 발생시킬 수 있는 부분이다. 두번째 단점은 코드가 길어지기 때문에 매개변수가 4개 이상이거나 꼭 필요할 때 사용하는 것이 바람직하다. (하지만 후에 매개변수가 많아지는 경우도 있다.) 매개변수가 길어서 눈에 띨 정도면 Builder pattern을 사용하자. 

결론: 클래스의 생성자나 static factory의 매개변수가 많아서 관리가 어려워지면 (특히 optional 매개변수가 많을 때) Builder pattern을 사용하자. 그러면 Client code는 telescoping pattern을 썼을 때보다 훨씬 가독성 높고 JavaBeans를 썼을 때보다 안전해질 것이다. 

댓글 없음:

댓글 쓰기