#01 생성자 대신 static 팩토리 메서드 사용을 고려하자
클래스의 인스턴스(instance)를 생성하기 위해서는 기본 생성자를 이용할 수 있습니다.
기본 생성자를 대신하여, public static 팩토리 메서드를 만들어서 인스턴스를 생성할 수 있습니다.
public static 팩토리 메서드를 이용한 인스턴스 생성의 장단점에 대해 알아보겠습니다.
생성자를 이용하다 보면, 클래스명이나 매개 변수만으로 해당 인스턴스를 파악하기 힘든 경우가 있습니다.
public static 팩토리 메서드를 이용하면 직접 이름을 지을 수 있기 때문에 좀 더 명확하게 인스턴스를 파악할 수 있습니다.
아래 코드를 보면 BigInteger(int, int, Random) 생성자를 통해 소수인 BigInteger 인스턴스를 생성할 수 있습니다.
하지만 생성자 만으로는 어떤 인스턴스인지 파악하기에 힘듭니다.
이 경우 BigInteger.probablePrime이라는 public static 팩토리 메서드를 이용하면 이해하기 더 수월해집니다.
기본 API 문서를 봐도 아래와 같이 기본 생성자가 아닌 별도의 메서드를 통해 구현하는 것을 추천합니다.
만약 동일한 매개변수를 통해 서로 다른 인스턴스를 생성해야 한다면, 생성자가 정의된 클래스의 문서를 봐야만 그 코드를 이해할 수 있을 것입니다.
아래와 같이 동일한 String 매개변수(경로 or 이름)를 통해 Video 인스턴스를 생성해야 한다면, 별도의 public static 팩토리 메서드를 통해 차이점을 부각하도록 만들 수 있습니다.
불변(immutable) 클래스의 경우 이미 생성된 인스턴스를 다시 이용할 수 있기 때문에 굳이 새로운 인스턴스를 재생성할 필요는 없습니다.
이를 위해 이미 생성된 인스턴스를 저장했다가 다시 이용할 수 있습니다.
예를 들어, Boolean.valueOf(boolean) 메서드에서는 아래와 같이 이미 만들어진 Boolean TRUE/FALSE 인스턴스를 이용하기 때문에 매번 Boolean 인스턴스를 재생성하지 않습니다.
만약 인스턴스 생성 시 리소스가 많이 소모되는 경우에는, 이와 같은 방법으로 성능을 향상할 수 있습니다.
public static 팩토리 메서드를 이용해 여러 번 호출되더라도 이미 생성된 동일 인스턴스를 반환하거나, 직접 제어하여 싱글톤이나 인스턴스 생성 불가 클래스로 만들 수 도 있습니다.
반환되는 객체의 클래스를 선택해야 할 때 뛰어난 유연성을 제공합니다.
개념이 모호한데 기본 생성자는 해당 클래스의 인스턴스만을 반환하는 반면, static 팩토리 메서드는 해당 클래스의 인스턴스뿐만 아니라 자신이 원하는 타입의 인스턴스를 반환할 수 있습니다.
public static 팩토리 메서드에서는 반환되는 인스턴스의 클래스가 public이 아닌 inner 클래스가 될 수 있고, static 팩토리 메서드에 전달되는 매개변수의 값에 따라 다양한 클래스의 인스턴스를 반환할 수 있습니다.
서브 타입에 해당되는 클래스라면 어떤 것이든 반환 가능합니다.
이를 통해 객체의 클래스를 버전에 따라 다양하게 활용할 수 있어서 유지보수가 용이하고 성능이 향상될 수 있습니다.
예를 들어, 아래와 같이 java.util.EnumSet 클래스는 public 생성자가 없고 static 팩토리 메서드들만 있으며, 크기에 따라 서로 다른 인스턴스를 반환합니다.
static 팩토리 메서드에서 열거형 요소수가 64개 이하면 RegularEnumSet, 65개 이상이면 JumnoEnumSet 인스턴스를 반환합니다.
이를 호출하는 클라이언트 입장에서는 static 팩토리 메서드 내부에서 실제로 생성되고 반환되는 클래스의 인스턴스를 알 수 없습니다.
만약 RegularEnumSet 보다 더 나은 클래스가 있다면, 추후 Java 버전에서 추가되어 static 팩토리 메서드를 통해 반환할 수 도 있습니다.
클라이언트 입장에서는 그저 EnumSet의 서브클래스라는 것만 인지하고 있으면 됩니다.
매개변수화 클래스의 생성자를 호출할 때는 타입 매개변수를 지정해야 합니다.
예를 들면 아래와 같이 생성자를 통하면 타입 매개변수를 두 번 지정해야 합니다.
Map<String, Integer> map = new HashMap<String, Integer>();
static 팩토리 메서드를 이용하면 컴파일러가 타입 매개변수를 해결하도록 할 수 있습니다.
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
책에서는 Java 6 기준으로 작성되었기 때문에 Map의 타입 추론이 안되는데, Java 8부터는 기본 지원하고 있습니다.
상속을 구현하려면 상위 클래스의 생성자를 호출해야 하는데, 접근할 수 없기 때문에 서브 클래스를 가질 수 없고 상속을 이용할 수 없습니다.
따라서 상속 대신 컴포지션을 이용해야 하는데, 이는 오히려 장점이 될 수 있습니다.
이에 대한 자세한 내용은 추후에 기재하겠습니다.
사용성 측면에서 단점이 발생할 수 있습니다.
생성자의 경우엔 클래스명과 동일하기 때문에 쉽게 구별할 수 있지만, static 팩토리 메서드는 다른 메서드와 섞여있어 잘 구분되지 않을 수 있습니다.
따라서 javadoc에서 정의하거나, 주석으로 표시하거나, 공통적인 작명 규칙을 만들어서 이를 해결해야 합니다.
static 팩토리 메서드의 공통 명칭 사용 예를 아래와 같습니다.
valueOf - 자신의 매개 변수와 같은 값을 갖는 인스턴스를 반환한다.
getInstance - 매개 변수에 나타난 인스턴스를 반환하지만, 매개 변수와 같은 값을 갖지 않을 수 있다.
싱글톤의 경우 매개 변수가 없고 오직 하나의 인스턴스만 반환한다.
newInstance - getInstance와 유사하나 반환되는 각 인스턴스가 서로 다르다.
static 팩토리 메서드는 public 생성자와 비교에 여러 장단점이 있습니다.
따라서 상황에 따라 static 팩토리 메서드를 잘 활용하는 것이 중요합니다.
작년 회고에 남겼듯이, 개인적으로 토이 프로젝트를 진행함과 동시에 Effective Java 책을 공부하고 정리하려 합니다.
주위에 물어보니 이 책을 읽다 보면 현자 타임이 매번 온다는데 완독 한번 해보렵니다 ㅎㅎ