기록공간

[Effective Java] Item01. 생성자 대신 정적 팩터리 메서드를 고려하라 본문

Java/Effective Java

[Effective Java] Item01. 생성자 대신 정적 팩터리 메서드를 고려하라

입코딩 2021. 12. 17. 17:11
반응형

인스턴스를 얻을 수 있는 전통적인 수단 ==> 생성자(Constructor)

하지만, 클래스는 생성자와 별도로 정적 팩터리 메서드를 제공한다. 

 

- 래퍼 클래스 Boolean에서 발췌한 간단한 예

// static(정적)
// valueOf(메서드)
public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

정적 팩터리 메서드 장점

1. 이름을 가질 수 있다.

 

생성자 자체 만으로는 반환될 객체의 특성을 제대로 설명하지 못한다.

하지만 정적 팩터리 메서드는 메서드명을 잘 짓는다면 객체의 특성을 쉽게 묘사할 수 있다.

기본 생성자            => BigInteger(int, int, Random)
정적 팩터리 메서드 => BigInteger.probablePrime(int, int, Random)    <--  어떤 의도인지 메서드 명을 통해 알 수 있다.

생성자가 여러개 있는 경우 각각 어떤 역할을 하는지 알기 힘들다. (문서를 보지 않는한...)

하지만 정적 팩터리 메서드를 사용하면 각 의미를 메서드명으로 녹여낼 수 있다.

 

 

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

 

위에서 살펴본 Boolean.valueOf(boolean) 메서드는 호출을 위해 객체를 아예 생성하지 않는다. 

JVM 실행 시점부터 객체정보가 이미 메서드 영역에 존재하고 있기 때문이다.

그렇기 때문에 같은 객체가 자주 요청되는 상황에서 성능을 상당히 끌어올려준다. (생성 비용이 클수록 더더욱..)

 

 

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

 

생성자와 다르게 반환할 객체의 클래스를 자유롭게 선택할 수 있다. 이는 엄청난 유연성을 가짐을 보여준다.

대표적인 예로 컬렉션 프레임웤 java.util.Collections가 있다. 

 

 

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

 

반환타입의 하위 타입이기만 한다면 어떤 클래스의 객체를 반환하든 상관없다.

대표 적인 예로 EnumSet이 있다.

원소가 64개 이하 => 원소들을 long 변수 하나로 관리한다. (RegularEnumSet 반환)
원소가 64개 이상 => long 배열로 관리한다. (JumboEnumSet 반환)

 

 

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

 

이런 유연함은 서비스 제공자 프레임웤을 만드는 근간이 된다.

네가지 대표적인 컴포넌트로 구성된다.

 

  1. 서비스 인터페이스 : 구현체의 동작을 정의한다.
  2. 제공자 등록 API : 제공자가 구현체를 등록한다.
  3. 서비스 접근 API : 클라이언트가 서비스의 인스턴스를 얻을 때 사용한다.
  4. 서비스 제공자 인터페이스 : 인터페이스의 인스턴스를 생성하는 팩터리 객체를 설명한다.

대표적인 예로 JDBC(Java Database Connectivity)가 있다.

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

그 외에 브리지 패턴과, 의존 객체 주입(DI)도 해당 특징을 가진다.

 

정적 팩터리 메서드 단점

1. 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

 

상속을 하려면 public이나 protected 생성자가 필요하기 때문이다.

 

 

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

 

생성자처럼 API 설명에 명확하게 드러나지 않는다.

그렇기 때문에 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다.

정적 팩터리 메서드에서 흔히 사용하는 명명 방식

- from : 매개변수를 하나 받아 해당 타입의 인스턴스를 반환하는 형변환 메서드
ex) Date d = Date.from(instant);

- of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 매서드
ex) Set<Rank> faceCard = EnumSet.of(JACK, QUEEN, KING);

- valueOf : from과 of의 자세한 버전
ex) BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

- instance, getInstance : 매개변수로 명시한 인스턴스를 반환, But 같은 인스턴스임을 보장하지 않음
ex) StackWalker luke = StackWalker.getInstance(options);

- create, newInstance : instance, getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환
ex) Object newArray = Array.newInstance(classObject, arrayLen);

- getType : getInstance와 같으나, 다른 클래스에 팩터리 메서드를 정의할 때 사용 Type은 반환할 객체의 타입이다.
ex) FileStore fs = Files.getFileStore(path);

- newType : newInstance와 같으나, 클래스에 팩터리 메서드를 정의할 때 사용 Type은 반환할 객체의 타입이다.
ex) BufferedReader br = Files.newBufferedReader(path);

- type : getType, newType의 간결한 버전
ex) List<Complaint> litany = Collections.list(legacyLitany);

 

정리

생성자와 정적 팩터리 메서드는 각자의 쓰임새가 있으므로 장단점을 잘 이해하고 적절히 사용하는것이 올바르다.
그렇다 하더라도 정적 팩터리 메서드를 사용하는 게 유리한 경우가 더 많으므로 
무작정 생성자를 제공하던 습관이 있다면 고치도록 한다.

반응형
Comments