(Effective Java 2/E) 101. Item 1 - Consider static factory methods instead of constructors

Consider static factory methods instead of constructors

  • 2판 제목 : 생성자 대신 정적 팩토리 메서드를 사용할 수 없는지 생각해보라.
  • 3판 제목 : 생성자 대신 정적 팩토리 메서드를 고려하라.

클래스를 통해 객체를 만드는 일반적인 방법은 public으로 선언된 생성자(constructor) 를 이용하는 것이다.

그 외의 방법으로 public으로 선언된 정적 팩토리 메서드(static factory method) 를 클래스에 추가하여 사용하는 것이다.

아래 예제는 자바의 Boolean 클래스의 valueOf 메서드이다.

1
2
3
4
@IntrinsicCandidate
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}

이 메서드는 boolean 값을 Boolean 객체에 대한 참조(references) 로 변환해준다.

정적 팩토리 메서드의 장점

생성자 외에 정적 팩토리 메서드를 제공하였을 때 얻을 수 있는 장점은 아래와 같다.

1. 생성자와는 달리 정적 팩토리 메서드는 이름(메서드명, name)이 존재한다.

이름이 존재한다는 것은 작명만 잘 한다면 코드의 가독성을 높여준다.

이름을 넣을 수 없는 생성자의 경우 유일한 시그니처를 유지해주어야 하기 때문에, 인자의 순서를 바꾸는 등의 방법으로 구별해야 하지만 이는 결코 권장되지 않으며 실수를 야기할 것이다.

2. 생성자와는 달리 호출할 때마다 새로운 객체를 생성할 필요가 없다.

불변 객체의 경우는 인스턴스를 미리 만들어놓거나, 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 재사용성을 끌어올린다.

정적 팩토리 메서드를 사용하면 같은 객체를 반복해서 반환할 수 있으므로, 특정 시점에 어떤 객체가 얼마나 존재할지 제어가 가능하다.

이러한 제어가 가능한 클래스를 개체 통제 클래스(instance-controlled class) 라고 하며 싱글턴(singleton) 이나 객체생성이 불가능한 클래스를 만드는 데 응용할 수 있다.

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

반환 타입의 하위 타입 객체를 반환함으로서, 반환되는 객체의 타입을 유연하게 결정할 수 있다.

예를 들어 public이 아닌 클래스의 객체를 반환시켜, 구현 세부사항을 감추는 인터페이스 기반 프레임워크(interface-based framework) 를 구현할 수 있다.

자바의 기본적인 자료구조를 제공하는 컬렉션 프레임워크도 좋은 예시이다.

4. 형인자 자료형(parameterized type) 객체를 편하게 만들 수 있다.

1
Map<String, List<String>> m = new HashMap<String, List<String>>();

위와 같이 자료형을 중복하면, 형인자가 늘어남에 따라 길고 복잡한 코드가 만들어진다.

이때 정적 팩토리 메서드를 사용하면 컴파일러의 타입 추론 능력에 기댈 수 있다.

1
2
3
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}

위와 같은 정적 팩토리 메서드를 이용해 아래와 같이 간소화 할 수 있다.

1
2
3
4
5
// AS-IS
Map<String, List<String>> m = new HashMap<String, List<String>>();

// TO-BE
Map<String, List<String>> m = HashMap.newInstance();

// 4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
// 반환하려는 타입의 하위 타입이기만 하면 어떤 클래스 타입이라도 반환할 수 있다.

정적 팩토리 메서드의 단점

장점이 있는만큼 당연히 단점도 있다.

1. 서브클래스를 만들 수 없다.

정적 팩토리 메서드는 public이나 protected로 선언된 생성자가 없으므로 하위 클래스를 작성할 수 없다.

2. 다른 정적 메서드와 명확하게 구별할 수 없다.

API 문서를 기준으로 볼때 생성자는 명백하게 구별되지만, 정적 팩토리 메서드와 정적 메서드는 구별되지않는다.

따라서 주석을 잘 작성하거나, 네이밍을 잘 하는 수 밖에 없다.

보통 정적 팩토리 메서드는 아래와 같은 이름으로 제공된다.

  • valueOf : 파라미터로 넘긴 값과 비교하여 같은 값을 갖는지 비교한다. (동등성 비교)
  • of : valueOf를 좀 더 간단하게 사용하였다. EnumSet 덕분에 보편화되었다.
  • getInstance : 인자에 기술된 객체를 반환한다. 싱글턴일 경우 인자를 요구하지않고 같은 객체를 반환한다.
  • newInstance : 호출할 때마다 새로운 객체를 생성하여 반환한다.
  • getType : getInstance와 같지만, 반환될 객체의 클래스가 아닌 다른 클래스에 팩토리 메서드가 존재할때 사용한다.
  • newType : newInstance와 같지만, 반환될 객체의 클래스가 아닌 다른 클래스에 팩토리 메서드가 존재할때 사용한다.

Migration 2/E to 3/E

  • 형인자 자료형 객체 생성의 장점이 제거되었다.
    • 컴파일러의 발전으로 Java 7부터 생성자 호출시에도 타입 추론이 가능해졌다.

References