Book

[Effective Java] Item 2. 생성자에 매개변수가 많다면 빌더를 고려하라

Jordy-torvalds 2021. 10. 6. 10:00
반응형

정적 팩토리 메서드와 생성자에 똑같은 제약이 하나 있다. 그것은 바로 매개변수가 많을 때 적절히 대응하기 어렵다는 점이다.

만약에, 식품 포장의 영양정보를 표현하는 클래스를 생각해보자. 영양정보는 1회 내용량, 총 n회 제공량, 1회 제공량당 칼로리 같은 필수 항목 몇 개와 총지방, 탄수화물, 나트륨 등 수많은 선택 항목으로 이뤄진다. 이때 대다수 값이 0 이다.

그래서, 값이 있는 항목에 한해 객체 생성시 넣을 수 있도록 생성자를 생성하려하면 파라미터가 끝도 없이 길어질 수 있고, 또한 파라미터에 넣는 값의 순서가 바뀌어도 오류가 나지 않아 어플리케이션이 비정상 적으로 작동될 수 있다. 더 큰 문제는 비정상 작동은 큰 문제가 된 이후에 발견 될 것이다.

결론적으로 생성자의 매개 변수 조합이 다양하고 매개 변수 수 자체가 많을 수록 클라이언트(클래스를 호출해서 사용하는 클래스) 코드를 작성하거나 읽기 어려워 질 것이다.

이러한 점을 보완하고자 점층적 생성자 패턴, 자바빈즈 패턴이 나오긴 했지만 둘 모두 비효율적이였다.

두 패턴을 설명하기 위해 아래와 같은 클래스가 있다고 가정하겠다.

```
public class Niniz{
 String name;
 int age;
    String job;
    String address;
}
```

**점층적 생성자 패턴**

클래스 내 변수들을 하나씩 늘려가며 생성자를 만드는 패턴. 주요 변수가 많아지면 수평으로 무궁무진하게 길어질 수 있다.

`public Niniz(String name){};`

`public Niniz(String name, int age){};`

`public Niniz(String name, int age, String job){};`

`public Niniz(String name, int age, String job, String address){};`

**자바 빈즈 패턴**

객체 생성 후 주요 변수를 모두 set 해주는 패턴. 변수가 많은 수록 더 수직으로 더 길어 질 수 있다.  
set이 끝나기 전까지는 객체가 불완전한 상태로 놓이게 되며, 그 과정에서 파라미터가 유효한지도 체크 할 수 없어 일관성 유지도 힘들다. 이러한 단점은 불변 클래스(객체 생성 후 변경 되지 않는 클래스)를 생성할 수 없게한다.

```
Niniz niniz= new Niniz();
niniz.setName("jordy");
niniz.setAge(2000);
niniz.setJob("Kakao Intern");
niniz.setAddress(null);
```

위 두 가지 패턴의 장점을 모두 취한 빌더 패턴이 있다.

-   클라이언트(빌더 패턴이 적용된 클래스를 사용하는 클래스)는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(혹은 정적 팩터리)를 호출해 빌더 객체를 얻는다.
-   그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 매개변수들을 설정한다.
-   마지막으로 매개변수가 없는 build매서드를 호출해 드디어 우리에게 필요한 (보통은 불변인) 객체를 얻는다.
-   빌더는 생성할 클래스 안에 정적 멤버 클래스 (public static class) 로 만들어두는게 보통이다.

아래 코드를 보자.

```
// 영양성분 빌더 패턴 클래스
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 sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int setvings){
            this.servingSize = servingSize;
            this.servings = servingSize;
        }

        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) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }

    @Override
    public String toString() {
        return "NutritionFacts{" +
                "servingSize=" + servingSize +
                ", servings=" + servings +
                ", calories=" + calories +
                ", fat=" + fat +
                ", sodium=" + sodium +
                ", carbohydrate=" + carbohydrate +
                '}';
    }
}

/* ------------------------------------------------------------------------------------*/

// Main 메소드
// 영양성분 클래스로 객체를 빌드 한 후 콘솔 확인.
public class Item2_Main {
    public static void main(String[] args) {
        NutritionFacts cocacola = new NutritionFacts.Builder(240,8)
                .calories(100).sodium(35).sodium(27).build();

        System.out.println(cocacola);

    }
}

/* [console] */
NutritionFacts{servingSize=240, servings=240, calories=100, fat=0, sodium=27, carbohydrate=0}
```

Main 메소드의 클라이언트 코드는 쓰기 쉽고 읽기가 쉽다. 빌더 패턴은 (파이썬과 스칼라에 있는) 명명된 선택적 매개변수를 흉내낸 것이다.

위 코드에 추가적으로 유효성 검사 코드를 가미하면 **완벽** 하다고 한다. 유효성 검사 코드는 빌더의 생성자와 메서드에서 입력 매개변수를 검사하고, build 메서드가 호출하는 생성자에서 여러 매개변수에 걸친 **불변식(invariant)**를 검사하자. 공격에 대비해 불변식을 보장하려면 빌더로부터 매개변수를 복사한 후 해당 객체 필드들도 검사해야 한다.

> **불변 immutable**  
> 어떠한 변경도 허용하지 않는 다는 뜻  
> ex)String

> **불변식 invariant**  
> 프로그램이 실행되는 동안, 혹은 정해진 기간 동안 반드시 만족해야 하는 조건을 말한다.

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

반응형