본문 바로가기
자바/이펙티브 자바

[Item1] 생성자 대신 정적 팩토리 메서드를 고려하라

by doflamingo 2020. 8. 16.

정적 팩토리 메소드

정적 팩토리 메소드란 그 클래스의 인스턴스를 반환하는 단순한 정적 메서드이다.

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

클래스는 클라이언트에 public 생성자 대신 (혹은 생성자와 함께) 정적 팩토리 메서드를 제공할 수 있다.

이 방식에는 장점과 단점이 있다.

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

생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못한다.

반면, 정적 팩토리 메서드는 아름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.

예를 들어, BigInteger(int, int, Random)BigInteger.prabablePrime 중 어느 것이 값이 소수인 BigInteger를 반환한다라는 의미를 잘 설명할 것 같은지 생각해보자.

하나의 시그니처로는 생성자를 하나만 만들 수 있다. 그러나 정적 팩터리 메서드에는 이러한 제약이 없다.

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

이 덕분에 불변 클래스(아이템17)는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.

Boolean.valueOf(boolean) 도 객체를 생성하지 않고 파라미터로 받은 걸 그대로 내보낸다.

반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩토리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제 할 수 있다.

이로 인해 싱글턴(아이템3)으로 만들 수 있고, 인스턴스화 불가(아이템4)로 만들 수도 있다.
또한, 불변 값 클래스(아이템17)에서 동치인 인스턴스가 단 하나 뿐임을 보장할 수 있다.

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

쉽게 말해서, 반환 타입은 인터페이스로 설정한 후 실제 구현 타입으로 반환하게 할 수 있다.

이는 인터페이스 기반 프레임워크(아이템20)를 만드는 핵심 기술이기도 하다.

java.util.Collections 하나만으로 자바 컬렉션 프레임워크가 제공하는 45개의 유틸리티 구현체를 사용 할 수 있다.
컬렉션 프레임워크는 이 45개의 구현 클래스를 제공하지 않기 때문에 API를 훨씬 더 가볍게 가져갈 수 있다.

자바 8부터는 인터페이스가 public static 메서드를 가질 수 있고, 자바 9에서는 private static 메서드까지 가질 수 있다. 그래서 자바 8부터 인터페이스에 public static 메소드를 사용해서 그 인터페이스의 구현체를 메소드를 제공할 수도 있다.

클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다.
그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수를 설정한다.

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

반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다. 심지어 다음 릴리스에서 또 다른 클래스의 객체를 반환해도 된다.

가령, EnumSet 클래스(아이템36)는 public 생성자 없이 오직 정적 팩토리만 제공하는데, OpenJDK에서는 원소의 수에 따라 64개 이하면 원소들을 long 변수 하나로 관리하는 RegularEnumSet의 인스턴스를, 65개 이상이면 long 배열로 관리하는 JumboEnumSet의 인스턴스를 반환한다.

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

이런 유연함은 서비스 Provider 프레임워크를 만드는데 근간이 된다. 그 예로 JDBC가 있는데 JDBC의 getConnection을 사용할 때 driver마다 다른 Connection 객체가 불려온다. 그렇기 때문에 정적 메서드를 작성하는 시점에는 어떤 driver class가 사용될지 몰라도 된다.

  • 서비스 Provider 프레임워크란

서비스 Provider 프레임워크는 3개의 컴포넌트로 이루어진다. 구현체의 동작을 정의하는 서비스 인터페이스(Service Interface), 제공자가 구현체를 등록할 때 사용하는 제공자 등록 API(Provider registration API), 클라이언트가 서비스 인스턴스를 얻을 때 사용하는 서비스 접근 API(Service access API) 클라이언트는 서비스 접근 API를 사용할 때 원하는 구현체의 조건을 명시할 수 있다. 조건을 명시하지 않으면 기본 구현체가 반환되거나 지원하는 구현체들을 하나씩 돌아가면서 반환한다. 3개의 핵심 컴포넌트와 더불어 종종 서비스 제공자 인터페이스(Service Provider Intercface) 가 쓰이기도 한다. 이 컴포넌트는 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체를 설명해준다. 서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션(아이템65)를 사용해야 한다.

JDBC에서는 Connection이 서비스 인터페이스 역할을, DriverManager.registerDriver가 제공자 등록 API 역할을, DriverManager.getConnection이 서비스 접근 API 역할을 한다.


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

앞서 이야기한 Collection 프레임워크의 유틸리티 구현 클래스는 상속할 수 없다는 이야기이다.

어찌 보면 이 제약은 상속보다 컴포지션을 사용(아이템 18)하도록 유도하고 불변 타입(아이템 17)으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점으로 받아들일 수 있다 .

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

생성자처럼 API에 명확히 드러나지 않으니 사용자는 정적 팩토리 메서드 방식 클래스를 인스턴스화 할 방법을 알아내야 한다.

이는 인터페이스 기반 프레임워크(아이템20)를 만드는 핵심 기술이기도 하다.

java.util.Collections 하나만으로 자바 컬렉션 프레임워크가 제공하는 45개의 유틸리티 구현체를 사용 할 수 있다.
컬렉션 프레임워크는 이 45개의 구현 클래스를 제공하지 않기 때문에 API를 훨씬 더 가볍게 가져갈 수 있다.

자바 8부터는 인터페이스가 public static 메서드를 가질 수 있고, 자바 9에서는 private static 메서드까지 가질 수 있다. 그래서 자바 8부터 인터페이스에 public static 메소드를 사용해서 그 인터페이스의 구현체를 메소드를 제공할 수도 있다.

클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다.
그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수를 설정한다.

댓글