[Item2] 생성자에 매개변수가 많다면 빌더를 고려하라

윤들윤들·2020년 12월 3일
0

EffectiveJava

목록 보기
2/17
post-thumbnail

정적 팩토리와, 생성자를 통해 인스턴스를 생성할때에는 공통된 제약이 하나있습니다.
바로 선택적 매개변수가 많을경우 적절한 대응이 어렵다는 점입니다.

다음과 같은 TimeUtil이 있다고 가정하겠습니다.
지금은 예로 만들어서 클래스 내부에 년월일시분초를 의미하는 6개의 프로퍼티가 존재합니다.

이 TimeUtil 인스턴스를 생성하려면 만들고자하는 매개변수를 모두 포함한 생성자중 가장 짧은 골라 호출해야합니다.

public class TimeUtil {
  private int year;
  private int month;
  private int day;
  private int hour;
  private int minute;
  private int second;

  public TimeUtil(int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
  }

  public TimeUtil(int year, int month, int day, int hour) {
    this.year = year;
    this.month = month;
    this.day = day;
    this.hour = hour;
  }

  public TimeUtil(int year, int month, int day, int hour, int minute) {
    this.year = year;
    this.month = month;
    this.day = day;
    this.hour = hour;
    this.minute = minute;
  }

  public TimeUtil(int year, int month, int day, int hour, int minute, int second) {
    this.year = year;
    this.month = month;
    this.day = day;
    this.hour = hour;
    this.minute = minute;
    this.second = second;
  }
}

위와 같은 클래스가 있다면 년월일초만 넣고 싶다면 다음과 같이 시간과 분에 필요없는 시간인 0을 넣어줘야 합니다. 지금은 6개의 프로퍼티로 예제를 작성해 와닿지 않을 수 있지만 20개 30개라고 생각하면 필요없는 매개변수가 엄청날것입니다.

TimeUtil time = new TimeUtil(2020,12,3,0,0,30);

따라서 이렇게 갯수가 많아지면 클라이언트 코드를 작성하거나 읽기 어려울 수 있습니다.
파라미터가 제대로 갯수에 맞게 넘어가는지 갯수도 확인하기 힘들고 이 파라미터가 어디로 넘어가는지도 제대로 알기 힘듭니다. 또한 매개변수의 순서가 변경되어도 타입이 같기 때문에 결국 런타임 시점에 사이드 이펙트가 발생할 수 있습니다.

자바빈즈 패턴( JavaBeans pattern)


자바빈즈 패턴은 매개변수가 없는 생성자로 객체를 생성한 후 setter 메서드들을 이용하여 원하는 매개변수의 값을 설정하는 방식입니다. 위에서 설명하던 TimeUtil을 자바빈즈 패턴으로 표현하면 다음과 같습니다.

public class TimeUtilJavaBeansPattern {
  private int year;
  private int month;
  private int day;
  private int hour;
  private int minute;
  private int second;

  public void setYear(int year) { this.year = year; }
  public void setMonth(int month) { this.month = month; }
  public void setDay(int day) { this.day = day; }
  public void setHour(int hour) { this.hour = hour; }
  public void setMinute(int minute) { this.minute = minute; }
  public void setSecond(int second) { this.second = second; }
}

TimeUtilJavaBeanspattern timeUtil = new TimeUtilJavaBeanspattern();
timeUtil.setYear(2020);
timeUtil.setMonth(12);
timeUtil.setDay(3);
timeUtil.setSecond(30);

하지만 위와 같이 자바빈즈패턴으로 만드는 방법에도 심각한 단점을 가지고 있습니다.
바로 하나의 객체를 만들기 위해 setter 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 객체의 일관성이 무너진 상태로 놓인다는 것입니다.

따라서 자바 빈즈 패턴에서는 클래스를 불변으로 만들 수 없고 스레드 안전성을 얻기 위해 개발자가 추가 작업을 해줘야합니다.

자 그러면 이제 이러한 단점들을 겸비한 빌더패턴이 등장합니다.

빌더 패턴 (Builder Pattern)


빌더패턴은 점층적 생성자 패턴의 안전성과 자바빈즈패턴의 가독성을 겸비하였습니다.
소스코드로 직접 봐보겠습니다.


public class TimeUtilBuilder {
  private final int year;
  private final int month;
  private final int day;
  private final int hour;
  private final int minute;
  private final int second;

  public TimeUtilBuilder(Builder builder) {
    this.year = builder.year;
    this.month = builder.month;
    this.day = builder.day;
    this.hour = builder.hour;
    this.minute = builder.minute;
    this.second = builder.second;
  }

  public static class Builder{
    private int year;
    private int month;
    private int day;
    private int hour;
    private int minute;
    private int second;

    public Builder year(int year) {
      this.year = year;
      return this;
    }

    public Builder month(int month) {
      this.month = month;
      return this;
    }

    ...

    public TimeUtilBuilder build() {
      return new TimeUtilBuilder(this);
    }
  }
}

빌더 패턴은 다음과 같이 필요한 객체를 직접 만드는대신 빌더 객체를 얻은다음 빌더 객체가 제공하는 setter메서들을 통해 매개변수 값을 설정합니다.
그다음 마지막에 build() 메서드를 호출해 우리가 사용할 TimeUtilBuilder 객체를 얻습니다. 이렇게 만들면 점층적 패턴과 자바빈즈패턴을 모두 사용한것처럼되고 객체의 불변성또한 보장이 됩니다.

또한 코드도 쓰기 쉽고, 가독성도 좋아집니다.

new TimeUtilBuilder.Builder()
    .year(2020)
    .month(12)
    .day(3)
    .build();

하지만 이러한 빌더패턴에도 항상 장점만 존재하는것이 아닙니다. 이유는 해당 객체를 만들기 위해서는 빌더부터 만들어야하고, 빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수도 있다고 합니다. 또한 매개변수가 4개 이상은 되어야 그 값어치를 하기 때문입니다.

API는 시간이 지날수록 매개변수가 많아지는 경우가 많으니 상황에 맞게 사용하면 될듯 합니다.

책에는 피자를 예로든 빌더패턴에 대한 예제가 있지만 패스 하도록 하겠습니다 !

profile
Front&BaackEnd를 재미있게 공부하고싶은 개발자 YundleYundle

0개의 댓글