[Effective Java] static factory method

dongbin_Shin·2022년 1월 21일
1

이펙티브 자바

목록 보기
1/5
post-thumbnail

Java에 대해 좀 더 깊이 알고 싶은 욕심이 생겨 이번 기회에 이펙티브 자바를 읽고 정리하려한다.
이펙티브 자바 3/E를 참고해 정리했다.

생성자 대신 정적 팩터리 메서드 고려하기

클라이언트가 클래스의 인스턴스를 얻기 위해 가장 먼저 생각할 수 있는 방법은 public 생성자가 있을 것이다. 하지만 그 외에도 우리는 static factory method로도 인스턴스를 얻을 수 있다.

클래스에서 클라이언트에 인스턴스를 제공할 때 생성자 대신 static factory method를 사용했을 때 장점과 단점이 있는데 이에 대해 정리해보자.

장점

1. 이름을 가질 수 있다.

생성자와는 다르게 static method는 메서드의 이름을 가질 수 있다.

이는 메서드를 통해 반환될 객체의 특성을 묘사할 수 있다.

Java4에서 추가된 BigInteger.probablePrime을 예로 들 수 있다.

BigInteger(int, int, Random)BigInteger.probablePrime 중 후자가 '값이 소수인 BigInteger를 반환한다'는 의미를 더 잘 표현한다는 것을 볼 수 있다.

또 한 클래스에 시그니처가 같은 생성자가 여러개 있을 때 이 API를 사용하는 개발자는 클래스 문서를 확인하지 않는다면 어떤 생성자를 호출해야할 지 판단하기 어렵다.

이런 경우 static method로 바꾼 후 각 메소드에서 반환하는 객체의 특징을 잘 드러내는 이름을 짓는 것이 좋다.

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

불변 클래스(Immutable class)는 static method를 이용해 객체를 반환함으로써 미리 만들어놓은 인스턴스를 반환하거나 캐싱을 활용해 재활용하는 방식을 취한다.

이 방법을 이용하면 같은 객체를 자주 요청하는 경우, 불필요한 객체 생성을 피할 수 있어서 성능을 올릴 수 있다.

Boolean.valueOf(boolean)

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

이 방식을 이용해 인스턴스 통제 클래스(instance-controlled class)를 구현할 수 있다. 인스턴스 통제 클래스란 반복되는 요청에 같은 객체를 반환해 언제 어느 인스턴스가 살아있게 할 지 통제할 수 있는 클래스를 말한다.

그렇다면 왜 인스턴스를 통제해야 할까?

우리는 인스턴스를 통제함으로써 클래스를 싱글톤(Singletone) 또는 인스턴스화 불가(noninstantiable)로 만들 수 있다. 또한 불변 값 클래스에서 동치인 인스턴스가 하나임을 보장할 수 있다. (a==b 일때만 a.equals(b))

3. 반환 타입의 하위 타입 반환 가능

이를 통해 반환할 객체의 클래스를 자유롭게 선택할 수 있는 유연성을 얻을 수 있다.

Java8 이후부터 인터페이스에 정적 메서드를 선언할 수 있게 되었기 때문에 이 장점을 활용할 수 있다.

API를 만들 때 이 유연성을 활용하면 구현 클래스를 공개하지 않아도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있다.

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

반환 타입의 하위 타입이면 어떤 클래스라도 매개 변수에 따라 반환이 가능하다. 더 나아가 릴리스마다 또 다른 클래스의 객체를 반환해도 된다.

EnumSet 클래스를 예로 들어 살펴보자.
EnumSet 클래스는 오직 static method로만 인스턴스를 제공하는데 원소의 수에 따라 두 가지 하위 클래스 중 하나의 인스턴스를 반환한다.

만약 원소가 64개 이하면 원소들을 long으로 관리하는 RegularEnumSet, 65개 이상이면 long배열로 관리하는 JumboEnumSet의 인스턴스를 반환한다.

만약 long으로 관리했을 때 이점이 없어지면 다음 릴리스때 모두 JumboEnumSet으로 반환하도록 바꿀수도, 다른 클래스를 하위 클래스로 더 추가하여 더 세분화해 반환하도록 바꿀수도 있다.

클라이언트는 Factory가 반환하는 객체가 어떤 클래스의 인스턴스인지 알수도 알 필요도 없다.

5. static method를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

이는 서비스 제공자 프레임워크 (Service Provider Framework) 를 만드는 근간이 된다.

서비스 제공자 프레임워크에서 제공자는 서비스의 구현체이다. 그리고 프레임워크가 이 구현체들을 클라인트에 제공하는 역할을 통제해, 클라이언트를 구현체로부터 분리해준다.

서비스 제공자 프레임워크는 3개(또는 4개)의 핵심 컴포넌트로 이뤄진다.

  • 서비스 인터페이스: 구현체의 동작을 정의
  • 제공자 등록 API: 제공자가 구현체를 등록할 때 사용
  • 서비스 접근 API: 클라이언트가 서비스의 인스턴스를 얻을 때 사용
    • 클라이언트는 서비스 접근 API를 사용할 때 원하는 구현체의 조건을 명시할 수 있다.
    • 명시하지 않으면 기본 구현체를 반환하거나 지원하는 구현체들을 하나씩 돌아가며 반환

이와 더불어 한가지 종종 쓰이는 추가 컴포넌트가 있다.

  • 서비스 제공자 인터페이스: 서비스 인터페이스의 인스턴스를 생성하는 Factory 객체를 설명해준다.

JDBC를 예시로 들 수 있다.

  • 서비스 인터페이스: Connection
  • 제공자 등록 API: DriverManager.registerDriver
  • 서비스 접근 API: DriverManager.getConnection
  • 서비스 제공자 인터페이스: Driver

단점

1. 상속을 하려면 생성자가 필요 (public or protected)

static factory method만 지원한다면 상속을 할 수 없다.
이 단점은 상속보다 컴포지션의 사용을 유도, 불변 타입을 만드려면 이 제약을 지켜야 하기 때문에 장점이 될 수도 있다.

2. 개발자가 찾기 어렵다.

생성자와 달리 static factory method는 API 설명에 명확히 드러나지 않기 때문에 이 API를 사용하려면 클래스에서 이 메소드를 찾아내야 한다.

규약처럼 널리 알려진 static factory method의 명명 방식

명명설명예시
from매개변수 하나를 받아서 해당 타입 인스턴스 반환Date d = Date.from(instant);
of여러 매개변수를 받아 적합한 타입의 인스턴스 반환Set set = EnumSet.of(JACK,QUEEN);
valueOffrom과 of의 더 자세한 버전BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance/getInstance(매개변수를 받으면)매개변수로 명시한 인스턴스 반환, 같은 인스턴스 보장 XStackWalker luke = StackWalker.getInstance(options);
create/newInstance매개변수로 명시한 인스턴스 반환, 매번 새로운 인스턴스 보장 OObject newArray = Array.newInstance(classObject, arrayLen);
getTypegetInstance와 같으나, 다른 클래스에 팩토리 메서드를 정의할 때 사용, Type은 팩터리 매서드가 반환할 객체 타입FileStore fs = Files.getFileStore(path);
newTypenewInstance와 같으나, 다른 클래스에 팩토리 메서드를 정의할 때 사용, Type은 팩터리 매서드가 반환할 객체 타입BufferedReader br = Files.newBufferedReader(path);
typegetType과 newType의 간결한 버전List litany = Collections.list(legacyLitany);
profile
멋있는 백엔드 개발자

1개의 댓글

comment-user-thumbnail
2022년 1월 21일

좋은 정리글이네요 ㅎㅎㅎㅎ

답글 달기