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

정적팩터리와 생성자의 문제?

선택적 매개변수가 많을 때 적절히 대응하기 어렵다.

문제해결하기

  • 점층적 생성자 패턴
    • 필수 매개변수만 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자 ... 이런 식으로 선택 매개변수를 전부 다 받는 생성자까지 늘려가는 방식.
    • 매개변수가 많아지면 코드를 작성하거나 읽기 어렵다.
public class Solution {
    private int a,b,c,d;

    Solution() { 
        this(0,0,0,0);
    }
    Solution(int a) {
        this(a,0,0,0);
    }
    Solution(int a, int b) {
        this(a,b,0,0);
    }
    ...
}
  • 자바빈즈패턴
    • 매개변수 없는 생성자로 객체를 만든 뒤 세터 메서드들로 각 값을 설정하는 것.
    • 객체 하나를 완성시키려면 메서드를 여러개 호출해야한다.
    • 객체가 완전히 생성되기 전까지 일관성(Consistency)이 무너진 상태가 된다.
    • 일관성이 무너지는 문제때문에 불변으로 만들 수 없다.
public class Solution {
    private int a,b,c,d;

    Solution() {}

    public void setA(int a) {
        this.a = a;
    }

    ...
}
  • 빌더패턴
    • 순서
      1. 필수 매개변수만으로 생성자(혹은 정적 팩터리)를 호출해 빌더객체를 얻는다.
      2. 빌더객체가 제공하는 일종의 세터메서들로 값을 설정한다.
      3. 마지막으로 매개변수가 없는 build메서드를 호출해서 완성된 객체를 얻는다.
public class Solution {
    private int a,b,c,d;

    public static class Builder {
        //필수매개변수
        private int a;

        //선택매개변수
        private int b = 0;
        private int c = 0;
        private int d = 0;

        Builder(int a) { this.a = a;    }

        Builder b(int b) { 
            this.b = b;
            return this;
        }
        Builder c(int c) {
            this.c = c;
            return this;
        }
        Builder d(int d) {
            this.d = d;
            return this;
        }

        Solution build() {
            return new Solution(this);
        }
    }

    private Solution(Builder builder) {
        this.a = builder.a;
        this.b = builder.b;
        this.c = builder.c;
        this.d = builder.d;
    }

    ...
}

정리

  • 생성자나 정적 팩터리가 처리해야할 매개변수가 많다면 빌더패턴을 선택하는 게 낫다.
  • 코드가 장황하고, 객체를 만들기 앞서 빌더 객체를 만들어야한다는 단점이 있지만, API는 시간이 지날 수록 매개변수가 많아지는 경향이 있음을 명심할 것. 애초에 빌더패턴으로 시작하는 편이 나을 때가 많다.

생성자 대신 정적 팩터리 메서드를 고려하라.

클래스의 인스턴스를 얻는 방법은 전통적으로 public생성자. 그 외에도public 생성자 외에도 클래스의 인스턴스를 반환하는 단순한 정적 메서드도 있다.

정적 팩터리 메서드의 장점

1. 이름을 가질 수 있다.

  • 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명 못하지만, 정적팩터리메서드는 반환될 객체의 특성을 이름으로 나타낼 수 있다
    • BigInteger(int num1, int num2, Random) <- 특성을 알 수 없다.
    • BigInteger.probablePrime <- 이름에 특성을 나타낼 수 있다.
  • 하나의 시그니쳐 == 하나의 생성자. 하지만 팩터리메서드는 이름을 줌으로써 각각의 차이를 드러내는 이름을 지어줄 수 있다.

2. 호출할 때마다 인스턴스를 새로 생성하지 않아도 된다.

  • 생성비용이 큰 객체가 자주 요청되는 상황이라면, 미리 만들어놓은 인스턴스를 재활용함으로써 성능을 끌어올릴 수 있다.
    • 플라이웨이트 패턴과 비슷한 기법.

3. 반환 타입의 하위 타입 객체를 반환할 수 있다.

4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

  • EnumSet 클래스는 public생성자 없이 정적팩터리만 제공.
    • 원소의 갯수에 따라 RegularEnumSet / JumboEnumSet 이란 EnumSet 클래스의 하위타입을 반환한다.

5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

  • JDBC를 생각해볼 것.

정적 팩터리 메서드의 단점

1. 상속을 위해선 public 또는 private 생성자가 필요하기 때문에 정적팩터리메서드만 제공하면 하위클래스를 만들 수 없다.

  • 어떻게보면 장점이기도하다. 상속보단 포함관계를 사용하도록 유도하기도 하고, 불변 타입을 만드려면 이 제약을 지켜야하기 때문

2. 정적 팩터리메서드는 프로그래머가 찾기 힘들다.

  • 생성자는 API 설명에 명확히 드러나있지만, 정적팩터리메서드는 그렇지 않다. 때문에 메서드 이름을 널리 알려진 규약에 따라 작성하는 것이 필요하다

정리.

정적 팩터리 메서드와 public 생성자는 각자 쓰임새가 있으니, 장단을 이해하고 사용하자. 하지만 정적팩터리메서드가 유리한 경우가 많으니, 무작정 public 생성자를 제공하는 습관이 있다면 고치자.

+ Recent posts