Effective Java - 객체 생성과 파괴(2)

SeungHyuk Shin·2021년 9월 5일
0

Effective Java

목록 보기
2/26
post-thumbnail

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


정적 팩터리와 생성자에는 똑같은 제약이 하나있다. 선택적 매개변수가 많을때 적절히 대응하기 어렵다는 점이다.

프로그래머들은 보통 점층적 생성자 패턴을 즐겨 사용했다.

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 NutrtionFacts(int servingSize, int servings){
    	this(servingSize, servigs, 0);
    }
    
    public NutrtionFacts(int sevingSize, int servings, int calories){
    	this(servingSize, servigs, caloreis, 0);
    }
    
    public NutrtionFacts(int sevingSize, int servings, int calories,int fat){
    	this(servingSize, servigs, caloreis, fat, 0);
    }
    public NutrtionFacts(int sevingSize, int servings, int calories,int fat, int sodium){
    	this(servingSize, servigs, caloreis, fat, sodium, 0);
    }
    
    public NutrtionFacts(int sevingSize, int servings, int calories,int fat,int sodium, int carbohydrate){
    	this.servingSize = servingSize;
        this.servings = sevings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
}

이 클래스의 인스턴스를 만드려면 원하는 매개변수를 모두 포함한 생성자중 가장 짧은것을 호출하면된다.

NutrtionFacts cocacola = new NutrtionFacts(240, 8, 100, 0, 35, 27);

보통 이런 생성자는 사용자가 원치 않은 매개변수까지 포함하기 쉬운데 어쩔수 없이 그런 매개변수에도 값을 지정해줘야 한다.

코드를 읽을때 각 값의 의미가 무엇인지 헷갈릴 것이고, 매개변수가 몇개인지도 주의해서 세어보아야 할것이다. 클라이언트가 실수로 매개변수의 순서를 바꿔 건네줘도 컴파일러는 알아채지 못하고, 결국 런타임에 엉뚱한 동작을 하게 된다.

이번에는 선택 매개변수가 많을때 활욜할 수 있는 두번째 대안인 자바빈즈 패턴을 보겠다.

public class NutrtionFacts{
	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 NutrtionFacts() {}
   // 세터 메서드들
   public void setServingsize(int val) { servingSize = val;}
   public void setServings(int val) { servings = val;}
   ... // 그 외의 메서드들
}

자바빈즈 패턴에서는 객체를 하ㅏ나 만들려면 메서드를 여러개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.

일관성이 깨진 객체가 만들어지면 버그를 심은 코드와 버그 때문에 런타임에 문제를 겪는 코드가 물리적으로 멀어져 디버깅도 힘들어진다.

이 처럼 자바빈즈패턴은 스레드 안정성을 얻으려면 프로그래머가 추가 작업을 해줘야만 한다.

다행히 우리에겐 세 번째 대안이있다.

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;

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

  public static void main(String[] args) {
    NutritionFacts cocaCola = new Builder(240, 8)
      .calories(100)
      .sodium(35)
      .carbohydrate(27)
            .build();
  }
}

NutritionFacts 클래스는 불변 클래스이고, 모든 매개변수의 기본 값들을 한 곳에 모아 두었고, Builder의 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출이 가능하다.

main 함수에서 보이는 것처럼 클라이언트는 NutritionFacts를 생성하기에 코드를 사용하기 쉽고 읽기도 쉽다는 것을 알 수 있다.

빌더 패턴을 사용하면서 중요한 사항은 잘못된 매개변수를 입력할 수도 있으므로 각 메서드에서 유효성 검사를 진행하도록 해야하며, 외부 공격을 대비하기 위해서 빌더로 부터 매개 변수를 복사한 후 객체를 생성하기 전에 해당 객체 필드들도 검사해봐야 한다.

그리고 검사 후 잘못된 점을 발견할 시 어떤 매개변수가 잘못 되었는지 자세히 알려주는 메시지를 담아 IllegalArgumentException(부적절한 매개변수를 가진 메서드)을 던진다.

[핵심정리] 빌더 패턴은 빌더 객체에 넘기는 매개변수에 따라 다른 객체로 만들 수 있어 매우 유연하다는 장점을 가지고 있고, 객체가 생성되는 시점에 객체가 완성되므로 불변 클래스도 만들 수 있다.
하지만 빌더 패턴을 이용하여 객체를 만들려면 빌더 객체 부터 만들어야 한다는 단점이 있는데, 빌더 객체의 생성 비용이 크지는 않지만 성능상 이슈가 나타날 수 있기 때문이다.
또한, 점층적 생성 패턴 보다는 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치가 있다. 하지만 실무에서 매개변수는 4개가 충분히 넘을 수 있다고 판단한다.
결과적으로, 빌더 패턴은 점층적 생성 패턴에 비해 클라이언트가 코드를 읽고 쓰기가 매우 간결하며 자바빈즈 패턴 보다 일관성 및 불변식 면에서 매우 안전하다는 것이다.

0개의 댓글