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

생성자와 비교하여 정적 팩토리 메서드가 가지는 장점은 아래와 같다.

  • 이름을 가질 수 있다.
  • 호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다.
  • 반환 타입의 하위 타입 객체를 반환 할 수 있다.
  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환 할 수 있다.
  • 정적 팩토리 메서드를 작성하는 시점에는 반활할 객체의 클래스가 존재하지 않아도 된다.

하나씩 살펴보도록하자.

이름을 가질 수 있다.

여러개의 생성자를 가지는 경우에도 생성자에는 이름을 부여할 수 없다. 단지 입력 매개변수에 따라 그에 해당하는 생성자가 맵핑될 뿐이다. 반면 정적 팩토리 메소드는 이름을 가질 수 있기에 보다 명확하고 반환될 객체의 특성을 쉽게 묘사 할 수 있다.

예를 들어 값이 소수인 BigInteger를 반환하는 객체를 만든다고 가정할 때 BigInteger(int, int, Random) 과 BingInteger.problePrime 중 어느 쪽이 의미를 잘 전달하는지 생각해보면 이름을 가진다는 것에 대한 이점을 이해 할 수 있을 것이다.

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

인스턴스를 반드시 새롭게 만들 필요가 없이 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.. 때문에 생성비용이 큰 객체가 자주 요청되는 상황에서 큰 성능향상을 노려볼 수 있으며, 인스턴스 통제가 가능하다.

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

생성자는 리턴값이 없는 반면, 정적 팩토리 메소드는 반환값을 가진다. 이 때 반환 객체를 하위 클래스로 지정 할 수 있다. 반환할 객체의 클래스를 자유롭게 선택할 수 있어 엄청난 유연성을 가진다.

class OrderUtil {

    public static Discount createDiscountItem(String discountCode) throws Exception {
        if(!isValidCode(discountCode)) {
            throw new Exception("잘못된 할인 코드");
        }
        // 쿠폰 코드인가? 포인트 코드인가?
        if(isUsableCoupon(discountCode)) {
            return new Coupon(1000);
        } else if(isUsablePoint(discountCode)) {
            return new Point(500);
        }
        throw new Exception("이미 사용한 코드");
    }
}

class Coupon extends Discount { }
class Point extends Discount { }

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

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

위 두가지는 아직까지 내 수준에서 이해가 제대로 가지 않았다. 추후에 이해가 되는 시점에 추가적인 내용을 보충 하겠다

이제 단점에 대해 살펴보자

상속이 불가능하다.

상속을 위해서는 public이나 protected 생성자가 필요하다. 때문에 정적 팩터리 메소드만으로는 하위 클래스를 만들 수 없다. 그어찌보면 이 제약은 상속보다 컴포지션을 사용하도록 유도하고, 불변타입으로 만들려면 이 제약을 지켜야한다는 점에서 오히려 장점으로 받아 들일 수 도 있다.

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

생성자처럼 API 설명에 명확하게 드러나지 않아, 사용자가 정적 메스드를 찾아내야한다. 이를 극복하기 위해 메서드 이름으로 널리 알려진 규약을 따라 짓는다. 규약은 아래와 같다.

  • from : 매개변수 하나를 받아서 해당 타입의 인스턴스 반환하는 형변환 메서드

    • ex) Date d = Date.from(instant)
  • of : 여러 매개변수를 받아, 적합한 타입의 인스턴스를 반환하는 집계 메서드

    • ex) Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • instance 혹은 getInstance : 매개변수로 명시한 인스턴스 반환, 같은 인스턴스임을 보장하지는 않는다.

    • ex) StackWalker luke = StackWalker.getInstance(options);
  • create 혹은 newInstance : 매번 새로운 인스턴스를 생성해 반환

    • ex) Object newArray = Array.newInstance(classObject, arrayLen);
  • getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용

    • ex)FileStore fs = Files.getFileStore(path);