본문 바로가기
Dev/Effective Java

2. 아이템[2] - 생성자에 매개변수가 많다면 빌더를 고려하라

by VIPeveloper 2021. 1. 10.
반응형

1. 서론

저번 시간 복습. 생성자 대신 정적 팩토리 메서드를 고려하는 방법 4가지에 대해 알아보았습니다.

 

캐싱, 이름, 하위클래스 리턴 가능, 매개변수에 따른 새로운 클래스 리턴 가능.

하지만 개발자가 이름을 알기 어려우므로 API 정리를 잘 해야하고, 정팩매만으로 클래스를 구성할 수 없다는 단점이 있었습니다.

2. 본론

이번 포스팅에서는 생성자에 매개변수가 많은 경우 이를 빌더를 통해 해결하는 방법에 대해 공부해보려 합니다. 책에서는 먼저 정적 팩토리와 생성자의 매개변수가 많을 때, 이를 어떻게 해결해야하는지에 대해 질문을 던지고 있습니다. 어떤 상황인지 이해하기 위해 간단히 코드를 가져와보겠습니다.


1. 점층적 생성자 패턴을 사용하기

public class NutritionFacts {
  private final int servingSize;  // (mL, 1회 제공량)     필수
  private final int servings;     // (회, 총 n회 제공량)  필수
  private final int calories;     // (1회 제공량당)       선택
  private final int fat;          // (g/1회 제공량)       선택
  private final int sodium;       // (mg/1회 제공량)      선택
  private final int carbohydrate; // (g/1회 제공량)       선택

  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);
  }

  public NutritionFacts(int servingSize, int servings,
                        int calories, int fat, int sodium) {
    this(servingSize, servings, calories, fat, sodium, 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;
  }
}

여기 NutritionFacts 객체가 존재합니다. 걍 딱봐도 엄청 많아보이네요. 이제 테스트 코드를 작성해보겠습니다.

class NutritionFactsTest {
    @Test
    @DisplayName("생성자 생성해보기")
    public void NutritionFactsTest1(){
        NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
    }
}

혼자 개발하는 것이라면 파라메터 순서를 다 외우고 있을지도 모릅니다. 하지만 먼 미래의 내가 이 코드를 본다면? 무슨 의미로 동작하는지 정확히 한번에 알기 매우 어려울 것이라고 생각합니다. 또한, 매개변수의 수와 순서가 맞는지를 일일이 확인해야한다는 단점도 존재합니다.

[그림01] 하지만 요즘 IDE는 이 단점을 멋지게 해결해줍니다!

 

이렇게 코딩하는 것은 파라메터가 적다면 편하게 할 수 있다는 장점이 있습니다.

 

2. 자바 빈즈 패턴을 사용하기

자바빈즈 패턴은 선택적 매개변수의 수가 많을 때 사용할 수 있는 패턴입니다.

매개변수가 없는 기본 생성자를 만들고 Setter를 호출하여 원하는 매개변수들을 설정해주는 방식입니다. 마찬가지로 코드를 통해 볼 수 있습니다.

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() { }
  // Setters
  public void setServingSize(int val)  { servingSize = val; }
  public void setServings(int val)     { servings = val; }
  public void setCalories(int val)     { calories = val; }
  public void setFat(int val)          { fat = val; }
  public void setSodium(int val)       { sodium = val; }
  public void setCarbohydrate(int val) { carbohydrate = val; }
}
    @Test
    @DisplayName("자바 빈즈로 생성자 생성해보기")
    public void NutritionFactsTest2(){
        NutritionFacts2 cocaCola = new NutritionFacts2();
        cocaCola.setServingSize(240);
        cocaCola.setServings(8);
        cocaCola.setCalories(100);
        cocaCola.setSodium(35);
        cocaCola.setCarbohydrate(27);
    }

점층적 생성자 패턴을 보완하였습니다. set 설정을 통해 이름을 부여하는 방식으로 구현한 패턴입니다.

 

하지만 자바빈즈 패턴의 경우에도 단점이 존재합니다.

 

. 객체를 하나 만들기 위해서는 메서드를 여러 개 호출해야 합니다.

이는 테스트 코드에서도 보듯 하나의 객체를 만들기 위해 기본 생성자를 구현하고, set을 계속 추가해줘야한다는 점을 보면 알 수 있습니다.

 

. 객체가 완전히 생성되기 전에는 객체의 일관성이 무너지게 됩니다.

일관성이 깨진다는 말은 한 번 객체를 생성할 때, 그 객체가 변할 가능성이 있다는 것입니다. 즉, Setter 메소드에 의해서 각 필드값들이 변경될 가능성을 열어뒀다는 것입니다.

 

3. 빌더 패턴을 사용하기

    @Test
    @DisplayName("빌더 패턴을 이용해서 생성해보기")
    public void NutritionFactsTest3(){
        NutritionFacts3 cocaCola = new NutritionFacts3.Builder(240, 8)
                .calories(100)
                .sodium(35)
                .carbohydrate(27)
                .build();
    }

이를 해결한 것이 빌더 패턴입니다. 위의 코드 처럼, 클라이언트는 필요한 객체를 직접 만드는 대신 필수 매개변수 만으로 생성자를 호출하여 빌더 객체를 만들 수 있습니다. 그 후 빌더 객체가 제공하는 메서드들을 호출하여 선택 매개변수에 값을 주는 작업을 합니다. 그리고 마지막으로 매개변수가 없는 build 메서드를 호출하여 처음에 필요로 했던 클래스

를 인스턴스화해줍니다.

이를 이용하게 되면 객체의 일관성을 지킬 수 있고, 메서드를 한번에 호출하게되어 자바빈즈 패턴의 단점을 해결할 수 있습니다.
class NutritionFacts3 {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    //Builder가 static class인 이유는 부모(NutritionFacts) 클래스의 생성여부와 상관없이 독립적으로 사용하기 위함.
    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 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 sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val){
            carbohydrate = val;
            return this;
        }

        public NutritionFacts3 build() {
            return new NutritionFacts3(this);
        }
    }
    private NutritionFacts3(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

 

3. 결론

결과적으로, 빌더 패턴은 점층적 생성 패턴에 비해 클라이언트가 코드를 읽고 쓰기가 매우 간결하며 자바빈즈 패턴 보다 일관성 및 불변식 면에서 매우 안전하다는 것이다.

 

출처1

 

[이펙티브 자바] 아이템 02.생성자에 매개변수가 많다면 빌더를 고려하라.(Re)

정적 팩터리와 생성자는 선택적 매개변수가 많을 때 적절히 대응하기가 어렵다는 점이 있다. 그럼 이런 선택적 매개변수가 많을 때 클래스는 어떤 식으로 대응을 할까? 점층적 생성자 패턴 사용

it-mesung.tistory.com

출처2

 

이펙티브 자바 - 2장 : 객체 생성과 파괴

2장 객체 생성과 파괴 아이템 1 : 생성자 대신 정적 팩터리 메서드를 고려하라 전통적으로 public 생성자를 사용하고 더 나아가 정적 팩터리 메서드를 제공할 수 있다. public class item1 { public static voi

otrodevym.tistory.com

출처3

 

반응형