Skip to main content

정적 팩토리 메서드(Static Factory Method)

정적 팩토리 메서드란 무엇인지, 그리고 어떤 장점과 단점이 있는지를 정리해보고자 한다.

정적 팩토리 메서드(Static Factory Method)

클래스 인스턴스를 반환하는 단순한 정적 메서드이다.
public 생성자를 사용하는 방법과 함께 클래스의 인스턴스를 생성하는 대표적인 방법 중 하나이다.

예시 - Boolean클래스의 valueOf

public static Boolean valueOf(boolean b) {
  // 인자로 받은 boolean 값에 따라 Boolean 클래스 인스턴스를 반환한다.
  return b ? Boolean.True : Boolean.FALSE;
}

Effective Java의 Item.2.에 따르면 생성자 대신 정적 팩터리 메서드를 고려하라 라는데, 이를 통해 얻는 이점과 실은 무엇일까?

정적 팩토리 메서드의 장점

1. 이름을 가질 수 있다.

반환될 객체의 특성을 제대로 설명하지 못하는 생성자와 달리, 정적 팩토리 메서드이름만 잘 지으면 어떤 특성을 지녔는지를 쉽게 묘사 할 수 있다.
또한, 생성자는 한 시그니처에 하나의 생성자만 만들 수 있다. 이와 달리, 정적 팩토리 메서드는 이름을 가질 수 있기 때문에 시그니처가 같은 생성자가 여러 개 필요한 경우 이름을 통해 구분지을 수 있다.

  • 시그니처: 함수의 이름, 공개 범위, 매개 변수 같은 함수에 대한 일반적인 정보. #
class A {
  int a
  // O
  public A(int a) {
    this.a = a;
  }
  // X : 이미 같은 시그니처의 생성자가 존재해 선언 불가
  public A(int b) {
    this.a = b;
  }
}

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

이러한 특성 덕분에 불변 클래스(immutable class)의 경우, (1)인스턴스를 미리 만들어 놓거나 (2)새로 생성한 인스턴스를 캐싱해 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
이를 활용해 같은 객체(특히 생성비용이 큰)가 자주 요청되는 상황에 성능을 끌어올릴 수 있다.

  • 불변 객체(Immuntable Objects): 모든 필드가 final, private이고, setter를 제공하지 않는 객체. 대표적인 예시론 String 클래스가 있다. #

또한, 언제 어느 인스턴스가 살아 있게 할지를 통제하는 인스턴스 통제 클래스(instance-controlled class) 와 같이 작동할 수 있다. 인스턴스를 통제하면 클래스를 싱글턴, 인스턴스화 불가로 만들 수 있다. 또한 불변 클래스에서 동치인 인스턴스가 단 하나임을 보장할 수 있다.
인스턴스 통제는 열거 타입이 인스턴스가 하나만 만들어짐을 보장한다.

3. 반환 타입의 하위 타입 객체를 반환 가능하다.

반환할 객체 클래스를 자유롭게 선택 할 수 있다. 이러한 유연함을 바탕으로 인터페이스 기반 프레임워크 를 만들 수 있다.

  • 인터페이스 기반 프레임워크 : 인터페이스와 그 구현으로 설계된 프레임워크로, 구현자(implementor)에겐 구현에 대한 완전한 제어권을 제공하고, 클라이언트에게는 안정적인 API를 제공하는 이점이 있다.

Java 8 이전에는 인터페이스에 정적 메서드를 선언할 수 없어서 인터페이스 XXX를 반환하는 정적 메서드가 필요하면, XXXs라는 동반 클래스 (companion class, 인스턴스화 불가한 클래스)를 만들어 그 안에 정의해왔다.

  • Collection(인터페이스) 과 Collections(동반 클래스)가 대표적인 예시다.

Collection의 경우 부가 기능들을 포함한 유틸리티 구현체들인터페이스대로 동작할 것임을 예상할 수 있기 때문에 구현체들을 공개하지 않았고, API를 작고 가볍게 만들 수 있었다.

Java 8 이후 로는 인터페이스가 디폴트 메서드와 정적 메서드를 가질 수 있게 되므로써 동반 클래스를 둘 이유가 없어졌다.
다만, public static만 가능하므로 별도의 package-private 클래스에 둬야 할 수도 있다.
Java 9 에서는 private static 메서드도 지원하지만, static 필드static 멤버 클래스여전히 공개되어 있어야 한다.

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

반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체건 상관없다.
일례로 EnumSet 클래스의 경우, public 생성자 없이 정적 팩토리만 제공하는데 원소의 수(64개 이하, 초과)에 따라 두 하위 클래스(RegularEnumSet, JumboEnumSet) 중 하나의 인스턴스를 반환한다.

  • 참고로, RegularEnumSetJumboEnumSet은 java 레퍼런스 사이트에는 명세되어 있지 않지만, IDE를 통해 EnumSet의 Declaration을 들어가보면 확인 가능하다.
    // EnumSet.class
        public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
            Enum<?>[] universe = getUniverse(elementType);
            if (universe == null)
                throw new ClassCastException(elementType + " not an enum");
    
            if (universe.length <= 64)
            // 원소의 개수가 64개 이하면 RegularEnumSet을 반환.
                return new RegularEnumSet<>(elementType, universe);
            else
            // 원소의 개수가 64개 초과면 JumboEnumSet을 반환.
                return new JumboEnumSet<>(elementType, universe);
        }
    

클라이언트는 정적 팩토리가 반환하는 객체가 어떤 클래스의 인스턴스인지 알 수도, 알 필요도 없다.
오직 반환 타입인 EnumSet의 하위 클래스이기만 하면 된다.

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

이런 유연함이 JDBC와 같은 서비스 제공자 프레임워크(Service Provider Framework)를 만드는데 근간이 된다.
서비스 제공자 프레임워크 는 서드파티가 서비스를 implement 하거나 extend제공자(provider, 서비스의 구현체)구현하도록 의도된 API이며, 클라이언트에 구현체 제공하는것을 통제해 클라이언트 / 구현체를 분리한다.

  1. 구현체의 동작 정의된 서비스 인터페이스(Service Interface) 를 구현하는 구현체를 만든다.
  2. 제공자가 구현체를 제공자 등록 API(Provider Registration API) 를 통해 등록한다.
  3. 클라이언트가 서비스 접근 API(Service Access API, 정적 팩터리 메서드) 를 이용해 서비스 인스턴스를 얻는다. 이때, 어떤 구현체를 원하는지 조건을 명시할 수 있다.

이러한 방식을 통해, 제공자가 추후 구현체를 작성하여 프레임워크에 제공해도 정상적으로 작동할 수 있다.

이렇듯 다양한 장점이 있는 정적 팩토리 메서드이지만, 단점 역시 존재한다.

정적 팩토리 메서드의 단점

1. 정적 팩토리 메서드만 제공하면 하위 클래스 생성이 불가능하다.

상속을 하려면 public 또는 protected 생성자가 필요하기 때문이다.
따라서 개발자는 상속보다 컴포지션 을 사용하도록 유도된다.

  • 컴포지션(Composition) : has-a 관계를 구현하기 위한 설계 기법으로, 객체가 다른 객체를 참조하는 인스턴스 변수를 사용하는 방법이다. #

2. 네이밍이 정확하지 않으면 다른 사람이 한 번에 알아채기 힘들다.

생성자 같이 API에 대한 설명이 명확히 드러나지 않아, 클라이언트는 정적 팩터리 메서드 방식을 채용한 클래스 의 인스턴스화 방법을 알아내야 한다.
따라서, API 문서의 명확한 작성적절한 네이밍 규칙을 통해 이를 방지해야 한다.