///
Search
💡

Item 1. 생성자 대신 팩토리 메서드를 고려하라

정적 팩토리 메서드란

정적 팩토리 메서드(static factory method)는 클래스의 인스턴스를 반환하는 정적 메소드이다. java의 박싱 클래스 중 하나인 Boolean은 다음과 같은 API를 제공한다.
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
Java
복사
주의사항 : 팩토리 메서드 패턴 과는 다르다!
객체의 인스턴스를 생성하는 가장 전통적인 방식은 new 키워드를 활용한 방식일 것이다.
public static void main(String args[]){ Boolean bool1 = new Boolean(true); // new 연산자를 사용한 방식 Boolean bool2 = Boolean.valueOf(true); // 정적 팩토리 메서드를 이용한 방식 }
Java
복사
정적 팩토리 메서드 방식을 사용하면 new 키워드의 사용을 방지하기 위해 private 생성자를 통해 new 키워드를 이용하지 못 하게 막는 경우가 많다.
정적 팩토리 메서드 방식이 new와 비교해 어떤 장단점이 있는지 살펴보자.

장점

장점 1. 이름을 가질 수 있다.

new 키워드는 인스턴스를 생성하는 것을 보장하지만, 싱글턴으로 생성된다, 반드시 새로운 인스턴스로 생성된다, 특정 타입으로 반환한다 등의 디테일은 담을 수 없다. 정적 팩토리 메서드 명을 통해 이름을 주어 반환될 객체의 특성을 묘사할 수 있다.

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

불변 클래스인 경우 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하는 식으로 불필요한 객체 생성을 피할 수 있다. 플라이 웨이트 패턴 과도 연관이 있다.

장점 3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

인터페이스의 구현체를 일일이 소개하지 않고도 상황에 맞는 서브클래스를 제공함으로써 API의 노출범위를 최소화 할 수 있다. 자바 8 이전에는 인터페이스에 정적 팩토리 메서드를 만들 수 없기 때문에, 동반 클래스를 활용했다. (ex. Collections) Collections.unmodifiableList(List list); 를 활용하면 사용하는 개발자는 UnmodifiableList 객체의 존재를 모르고도 해당 효과를 누릴 수 있다.

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

오버라이딩을 활용해서 매개변수가 다르면 다른 서브클래스를 반환할 수 있다. 다음 릴리스에선 다른 구현체를 제공해도 된다. EnumSet의 예시에서, EnumSet은 원소 개수가 64개 이하면 RegularEnumSet이라는 구현체를, 그 이상이면 JumboEnumSet의 인스턴스를 반환한다.

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

위의 장점을 통해 서비스 제공자 프레임워크를 만드는 근간이 된다. 서비스 제공자 프레임워크는 JDBC 처럼 클라이언트에 구현체를 제공하는 역할을 프레임워크가 통제하여, 클라이언트에게 필요하지 않을 땐 기본 구현체를, 요구사항이 있을 땐 특정 구현체를 반환할 수 있다.

단점

단점 1. 상속을 하려면 public 혹은 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.

정적 팩토리 메서드를 제공하면 private 생성자를 활용하게 되므로 상속을 이용할 수 없다. 이 제약은 상속보다는 조합 원칙 혹은 불변 타입을 만들려면 이 제약을 지켜야 한다는 점에서 장점으로 받아들일 수 도 있다.

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

생성자처럼 인스턴스에 API 설명에 명확히 드러나지 않으니 사용자가 찾기 어렵다. (요즘은 IDE가 잘 처리해주지만...) 그래서 혼란을 줄이기 위해 관례적인 네이밍을 몇 가지 활용하기도 한다.
메서드명
내용
용례
from
매개 변수를 하나 받아서 해당 타입의 인스턴스를 반환한다.
Date date = Date.from(instant);
of
여러 매개 변수를 받아 적합한 타입의 인스턴스를 반환한다.
Set<Rank> faceCards = Set.of(JACK, QUEEN, KING);
valueOf
from과 of의 더 자세한 버전
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance || getInstance
(매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.
StackWalker luke = StackWalker.getInstance(options);
create || newInstance
instance || getInstance 와 같지만, 새로운 인스턴스의 생성을 보장한다.
Object newArray = Array.newInstance(classObject, arrayLen);
getType
getInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. "Type"은 반환할 타입의 클래스를 적는 것이다.
FileStore fs = FileStore.getFileStore(path);
newType
newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스의 팩토리 메서드를 정의할 때 쓴다. "Type"은 반환할 타입의 클래스를 적는 것이다.
BufferedReader br = Files.newBufferedReader(path);
type
getType과 newType의 간단한 버전
List<Complaint> litany = Collections.list(legacyLitany);

마지막 정리

정적 팩터리 메서드와 public 생성자(new)는 각자의 쓰임새가 있으므로 상대적인 장단점을 이해하고 사용하자. 하지만 정적 팩터리 메소드의 장점이 많기 때문에 무조건 public 생성자를 제공하던 습관이 있다면 고치자.

용어 정리

한글명
영어명
정적 팩토리 메서드
static factory method
인스턴스
instance
API
Application Programming Interface
박싱 클래스
boxed class
팩토리 메서드 패턴
factory method pattern
싱글턴
singleton
불변 클래스
immutable class
동반 클래스
companion class
서비스 제공자 프레임워크
service provider framework
상속
inheritance