2장 객체의 생성과 삭제

규칙 2. 생성자 인자가 많을 때는 Builder 패턴 적용 또한 고려하자.

 

static factory method와 constructor는 둘 다 선택적 인자가 많은 경우에 적응하기 힘들다는 공통적인 단점을 가지고 있다.

 

1. 점층적 생성자 패턴(telescoping constructor pattern)

선택적 인자가 10여개가 넘을 시, 보통 점층적 생성자 패턴(telescoping constructor 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 NutritionFacts(int servingSie, int servings){
    this(servingSize, servings, 0);
  }
  
  public NutritionFacts(int servingSie, int servings, int calories){
    this(servingSize, servings, calories, 0);
  }
  
  public NutritionFacts(int servingSie, int servings, int calories, int fat){
    this(servingSize, servings, calories, fat, 0);
  }
  
  public NutritionFacts(int servingSie, int servings, int calories, int fat, int sodium){
    this(servingSize, servings, calories, fat, sodium, 0);
  }
  
  public NutritionFacts(int servingSie, 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;
  }
}

정말 그지같다.

코드만 봐도 알 수 있다싶이, 점층적 생성자 패턴은 인자수가 증가 할 수록 작성하기도, 읽기도 어려운 코드가 된다.

특히 코드만 봐서 해당 인자가 어떤 인자인지 알 기가 힘들다. (인자의 순서를 하나씩 세어봐야 한다.)

 

2. 자바빈 패턴(JavaBeans Pattern)

두번째 방법으로는 자바빈 규약에 따르는 클래스를 생성 한 뒤, setter 메서드로 각 값들을 설정해 주는 방법이 있다.

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 NutritionFacts(){}

  //Setter Method
  public void setServingSize(int servingSize){ this.servingSize = servingSize; }
  public void setServings(int servings){ this.servings = servings; }
  public void setCalories(int calories){ this.calories = caloires; }
  public void setFat(int fat){ this.fat = fat; }
  public void setSodium(int sodium){ this.sodium = sodium; }
  public void setCarbohydrate(int carbohydrate){ this.arbohydrate = arbohydrate; }
}

자바빈 패턴은 점층적 생성자 패턴에 있던 문제는 없지만, 1회의 함수 호출로 객체 생성을 마칠 수 없으므로 객체 일관성이 깨질 수 있다.

또한 자바빈 패턴은 immutable 클래스를 만들 수 없다는 단점이 있다.

 

3. 빌더 패턴 (Builder Pattern)

빌더 패턴은 점층적 생성자 패턴의 안전성과 자바빈 패턴의 가독성을 결합한 대안이다.

객체를 생성하는 빌더 클래스(builder class)를 만들고, 이 생성자에 필수 인자를 전달하여 빌더 객채(builder object)를 만든다.

그 후 빌더 객체에 정의된 setter method를 호출하여 선택적 인자들을 추가하고 build 메서드를 호출하여 immutable 객체를 만든다.

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;
  
  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;
  }
  
  //builder class
  public static class Builder {
    //필수 인자
    private final int servingsize;
    private final int servings;
    //선택적 인자(초기화)
    private final int calories = 0;
    private final int fat = 0;
    private final int sodium = 0;
    private final int carbohydrate = 0;
   
    public Builder(int servingSize, int servings){
      this.servingSize = servingSize;
      this.servings = servings;
    }
    
    public Builder calories(int calories){
      this.calories = calories;
      return this;
    }
    
    public Builder fat(int fat){
      this.fat = fat;
      return this;
    }
    
    public Builder sodium(int sodium){
      this.sodium = sodium;
      return this;
    }
    
    public Builder carbohydrate(int carbohydrate){
      this.carbohydrate = carbohydrate;
      return this;
    }
    
    public NutritionFacts build(){
      return new NutritionFacts(this);
    }
  }  
}

해당 코드는 모든 인자가 final로 immutable한 NutritionFacts 객체이다.

위에서 작성한 빌더 패턴은 객체 자신을 반환하므로 다음과 같이 chaining하여 사용 할 수 있다.

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

 

빌더 패턴의 장점

또한 빌더패턴을 적용하면서 얻을 수 있는 장점은 다음과 같다.

  1. 불변식(invariant)을 적용 할 수 있다.

    build 메서드, 혹은 build가 호출 되기 전에 불변식에 위반되었는지 검사 할 수 있다.

    public NutritionFacts build(){
      if(/*불변식 검사*/){
        throw new IllegalArgumentExcepiton();
      }
      return new NutritionFacts(this);
    }
    
    public Builder carbohydrate(int carbohydrate){
      if(/*불변식 검사*/){
        throw new IllegalArgumentExcepiton();
      }
      this.carbohydrate = carbohydrate;
      return this;
    }
    
  2. 객체를 생성 할 때 여러 개의 가변인자(varargs, ex : String…)를 인자로 받을 수 있다.

    가변인자는 생성자 및 메서드에 하나만 사용 가능하다. 하지만 빌더 패턴을 적용하면 각 선택적 인자의 setter 메서드에 가변 인자를 적용 할 수 있다.

    (기본적으로 메서드 시그니쳐의 마지막 인자에만 지정한다.)

  3. 빌더 패턴은 유연하다.

    하나의 빌더 객체로 여러 객체를 만들 수 있다.

    기존에 사용했던 빌더 객체에서 setter 메서드로 값만 바꾼 후 build 하면 다른 객체가 생성된다.

    또한 빌더 객체는 어떤 필드의 값은 자동으로 채우도록 할 수 있다.(예 : 자동으로 증가하는 일련 번호 등.)

  4. 인자가 설정된 빌더는 훌륭한 추상적 팩토리이다.

    클라이언트는 빌더를 다른 메서드에 넘겨서 하나 이상의 객체를 만들도록 할 수 있다.

    다음과 같은 제네릭을 적용한 인터페이스면 어떤 객체를 만드는 빌더냐에 관계 없이 모든 빌더에 적용 할 수 있다.

    (빌더 객체를 인자로 받는 모든 메서드에 전달 할 수 있다.)

    public interface Builder<T> {
      public T build();
    }
    

    빌더 객체의 인자를 제한 할 경우, 보통 한정적 와일드카드 자료형을 통해 인자의 자료형을 제안한다.

    ex ) Tree buildTree(Builder<? extends Node> nodeBuilder){ … }

 

빌더 패턴의 단점

객체를 생성 하려면 우선 빌더 객체를 생성해야한다는 단점이 있다.

실무에서는 빌더 객체 생성에 의한 오버헤드의 문제는 없겠지만, 성능이 중요한 상황에선 그렇지 않을 수 도 있다.

또한 점층적 생성자 패턴에 비해 만흔 코드를 요구하기 때문에 인자가 충분히 많은 상황에서 이용해야 한다.

'Effective Java > 2장. 객체의 생성과 삭제' 카테고리의 다른 글

규칙 2 . Builder Pattern  (0) 2018.01.14
규칙 1 . Static Factory Method  (0) 2018.01.03

Effective Java

2장 객체의 생성과 삭제

규칙 1. 생성자 뿐만 아니라 정적 팩터리 메서드의 사용도 고려하자.

객체를 생성할때 일반적으로 public으로 선언되어있는 생성자를 사용한다.

하지만 생성자 외에 static factory method(정적 팩토리 메서드)를 사용해서 객체를 생성 할 수 있다.

 

정적 팩토리 메서드의 장점

  1. 생성자와 달리 static factory method에는 이름이 존재한다.

    해당 메서드가 어떤 객체를 생성하는지 알 수 있어 가독성을 높일수 있다.

  2. 생성자와 달리 호출 될 때마다 새로운 객체를 생성할 필요가 없다.

    • 이뮤터블 클래스일 경우 이미 만든 객체를 활용할 뿐만 아니라 객체를 캐시하여 재사용 하여 객체를 불필요하기 생성하는 것을 피할 수 있다.
    • 특정시점에 객체가 얼마나 존재하는지 제어할수 있다.(instance-controlled class). 이를 통해 싱글턴 패턴 클래스, 객체 생성이 불가능한 클래스 생성 등을 만들수 있다. 또한 이뮤터블 클래스일 경우 같은 객체가 존재하지 않도록 할 수 있다.
  3. 생성자와 달리 자료형의 하위 자료형 객체를 반환 할 수 있다.

    • 반환되는 객체의 클래스를 유영하게 결정 할 수 있다.

    • 구현 세부 사항을 감출수 있으므로 간결한 API가 가능하며 이는 인터페이스 기반 프레임 워크에 적합하다.

    • 대표적인 예 : JDK 1.5에 도입된 EnumSet -

      EnumSet은 public 생성자가 없으며 정적 팩토리 메서드만 가지고있다.

      EnumSet은 정적 팩토리 매서드로 두가지 구현체 중 하나를 반환한다.

      • RegularEnumSet : enum 상수들이 64개 이하일경우, enum의 갯수를 long 변수 하나만 사용해 표현한다.
      • JumboEnumSet : enum상수들이 64개 이상일 경우, enum의 갯수를 long형 배열을 사용해 표현한다.
    • Service Provider Framework : 정적 팩토리 메서드가 작성된 시기에 반환될 구현체 클래스가 존재하지 않아도 무방하다.

      • 대표적인 예 : JDBC

      • Service Provider Framework는 세가지 핵심 컴포넌트로 구성된다.

        • Service Interface : 서비스 제공자가 구현한다. (JDBC : Connection)
        • Provider Registration API : 구현체를 서비스에 등록하여 클라이언트가 사용 가능하도록한다. (JDBC : DriverManger.registDriver)
        • Service Access API : 클라이언트에게 서비스 구현체를 제공한다.(DriverManger.getConnection)
        • Service Provider Interface : 선택적이며, 서비스 제공자가 구현한다. 서비스 구현체의 객체를 생성하기 위한 인터페이스이다. 이가 없으면 자바의 리플렉션등으로 객체가 생성된다.(JDBC : Driver)
  4. 형인자 자료형(parameterized type) 객체를 만들 때 편리하다.

    형인자 자료형 클래스의 생성자를 호출할 때는, 문맥상 형인자가 명백하더라도 반드시 인자로 형인자를 전달해야한다.

    Map<String, List<String>> m = new HashMap<String, List<String>>();
    

    이처럼 형인자가 늘어남에 따라 길고 복잡한 코드가 만들어진다. 하지만 정적 팩토리 메서드로 컴파일러가 형인자를 유추하도록 할 수 있다. 이를 자료형 유추(type interface)라고 부른다.

    public static <K, V> HashMap<K, V> newInstance(){
      return new HashMap<K, V>();
    }
    Map<String, List<String>> m = HashMap.newInstance();
    

JDK 1.7 이후부터는 생성자를 이용해서도 형인자 유추가 가능하다.

 

정적 팩토리 메서드의 단점

  1. 정적 팩토리 메서드만 있는 클래스는 public이나 protected로 선언된 생성자가 없으므로 하위 클래스를 만들 수 없다.
  2. 정적 팩토리 메서드가 다른 정적 메서드와 확연히 구분되지 않는다.

요약

정적 팩터리 메서드와 public 생성자는 용도가 서로 다르다. 그 차이점과 장단점을 이해하고 정적 팩토리 메서드가 효과적인 경우가 많으니 무조건 public 생성자만 만드는 것은 삼가자.

'Effective Java > 2장. 객체의 생성과 삭제' 카테고리의 다른 글

규칙 2 . Builder Pattern  (0) 2018.01.14
규칙 1 . Static Factory Method  (0) 2018.01.03

+ Recent posts