티스토리 뷰

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

 정적 팩토리 메서드는 클래스의 인스턴스를 반환하는 정적 메서드를 의미한다. 다음은 Integer 클래스의 정적 팩토리 메서드 예시이다.

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

 일반적으로 클라이언트가 클래스의 인스턴스를 얻고자 할 때에는 new 키워드를 활용한 생성자를 호출한다. 그러나 위와 같이 정적 팩토리 메서드를 활용하면 다음과 같은 이점을 얻을 수 있다.


정적 팩토리 메서드가 생성자보다 좋은 장점

1. 이름을 가질 수 있다.

 생성자는 메서드의 한 종류이지만, 클래스의 명을 그대로 사용해야 한다는 제약이 존재한다. 이로 인해 인스턴스 생성의 용도를 표현할 수 있는 시그니처는 메서드의 파라미터로 제한된다. 반면에 정적 팩토리 메서드를 활용하면 반환될 객체의 특성을 메서드 이름을 통해 쉽게 묘사할 수 있다.

RandomNumber evenNumber = new RandomNumber(oddEvenTypeCode);
RandomNumber evenNumber = RandomNumber.createEven();

 위 코드의 문장은 무작위 숫자를 생성하고자 하는 의도를 표현한다. 첫 번째 라인은 전통적인 public 생성자를 활용하여 클래스의 인스턴스를 생성했다. 두 번째 라인은 정적 팩토리 메서드를 활용했다. 두 번째 라인은 '짝수를 생성한다' 라는 의도를 메서드 명으로 표현했기 때문에 가독성이 높다. 첫 번째 라인은 이 문장을 통해 짝수 또는 홀수가 생성될 지 명확히 알 수가 없다. 의도를 표현하기 위한 다른 수단이 필요하다는 의미이다.

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

 정적 팩토리 메서드는 인스턴스를 직접 할당하는 new 키워드를 사용하지 않는다. 따라서 인스턴스 획득 과정에 '캐싱' 개념을 구현할 수 있다.

public final class Boolean implements java.io.Serializable, Comparable<Boolean> {

    public static final Boolean TRUE = new Boolean(true);

    public static final Boolean FALSE = new Boolean(false);
   
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
}

 위 코드는 논리 자료형의 wrapper 클래스 Boolean 의 구현 코드이다. valueOf 라는 정적팩토리 메서드는 new 연산자를 사용하지 않고, 기 할당된 TRUE 또는 FALSE 인스턴스를 리턴한다.

 

 인스턴스를 캐싱하는 기법은 불변클래스(Immutable Class) 구현방법 중 하나이다. 정적 팩토리 메서드를 활용해 미리 캐싱한 인스턴스를 리턴함으로써, 클래스의 필드값이나 구성요소(ex) 컬렉션 멤버 구성) 변경할 수 없는 불변클래스를 구현할 수 있다,

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

 부모 클래스의 정적 팩토리 메서드는 구현 클래스의 타입을 공개하지 않으면서 인스턴스를 리턴할 수 있다. 이와 같은 방법으로 메서드를 설계할 경우 API를 작게 유지할 수 있다. 또한 구현클래스의 타입을 공개하지 않는 형태의 설계는 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 하다.

 

 자바 8 이후 버전부터는 인터페이스가 정적 메서드를 가질 수 있게 되기 때문에, 인터페이스 기반 메서드를 정의하는 것이 더욱 용이해졌다. 프로그래머는 명시한 인터페이스대로 동작하는 객체를 얻을 것임을 알기 때문에, 굳이 별도의 문서를 찾아가며 실제 구현 클래스가 무엇인지 알아보지 않아도 된다.

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

 EnumSet 클래스는 원소의 수를 입력 파라미터로 하여 반환 클래스의 인스턴스 레이아웃을 결정한다. 원소의 수가 64개 이하일 때에는 원소들을 long 변수로 관리하는 ReqularEnumSet 클래스의 인스턴스를, 65개 이상일 경우에는 long 배열로 관리하는 JumboEnumSet 클래스의 인스턴스를 반환하는 식이다.

 

 클라이언트는 이와 같은 구현체 클래스의 존재를 모른다. 따라서 다음 릴리즈때는 클래스를 삭제하거나, 다른 구조를 갖는 클래스를 추가해도 아무 문제가 없다. 반환체가 EnumSet의 하위 클래스이기만 하면, 클라이언트는 팩터리가 건네주는 객체가 무엇인 지 알 필요가 없다.

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

 이러한 유연함은 서비스 제공자 프레임워크를 만드는 근간이 된다. 서비스 제공자 프레임워크는 서비스의 구현체가 제공자로서의 역할을 하게 된다. 이 제공자들을 클라이언트에 제공하는 역할을 프레임워크가 통제하여, 클라이언트를 구현체로부터 분리해준다. 대표적으로는 JDBC가 있다. 깊게 생각해보면 스프링의 DI도 서비스 제공자의 역할을 프레임워크가 통제하기 위한 하나의 방법이다.

 

 클라이언트는 서비스 접근 API를 사용할 때 원하는 구현체의 조건을 명시할 수 있다. 조건을 명시하지 않으면 기본 구현체를 반환하거나 지원하는 구현체들을 하나씩 돌아가며 반환한다.


정적 팩토리 메서드의 단점

1. 상속을 위해서는 public 또는 protected 메서드가 필요하기 때문에, 정적 팩토리 메서드만 제공한다면 하위 클래스를 만들 수 없다.

 이 제약은 상속보다 조합의 설계를 하거나, 불변타입을 사용하도록 유도하기 때문에 오히려 장점이 될 수 있다.

2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다

 정적 팩토리 메서드는 생성자처럼 API 설명에 명확히 드러나지 않기 때문에, 사용자는 정적 팩토리 메서드 방식으로 인스턴스 획득을 설계한 클래스의 인스턴스화 방법을 알아내야 한다.

※ 정적 팩토리 메서드 Naming Convention

  •  from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
    ex) Date d = Date.from(instant)
  • of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
    ex) Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf : from 과 of 의 더 자세한 버전
    ex) BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance 혹은 getInstance : (매개변수를 받는다면) 배개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
    ex) StackWalker luke = StackWalker.getInstance(options);
  • create 혹은 newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
    ex) Object newArray = Array.newInstance(classObject, arrayLen);
  • getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 쓴다. "Type"은 팩토리 메서드가 반환할 객체의 타입을 의미한다.
    ex) FileStore fs = Files.getFileStore(path);
  • newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 쓴다. "Type"은 팩토리 메서드가 반환할 객체의 타입을 의미한다.
    ex) BufferedReader br = Files.newBufferedReader(path);
  • type : getType과 newType 의 간결한 버전
    ex) List<Complaint> litany = Collections.list(legacyLitany);

정적 팩토리 메서드와 public 생성자는 각자의 쓰임새가 있으므로 상대적인 장단점을 이해하고 사용하는 것이 좋다.
그렇다고 하더라도 정적 팩토리 메서드를 사용하는게 유리한 경우가 더 많으므로, 무작정 public 생성자를 제공하던 습관이 있다면 고치자.

마치며

 최근에 클린코드, OOP 관련내용을 공부하면서, 정적 팩토리 메서드를 작성해보기 시작했다. 그런데 정적 팩토리의 장점 중 2~3 가지 요소만 생각을 했던 것 같다. 위에서 공부한 장점들을 활용해볼 수 있도록 더욱 다양한 경우로 해당 기법을 적용해 보아야 겠다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday