Book Record/Effective Java 3E

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

lakelight 2022. 11. 30. 09:47
728x90
반응형

 

클래스는 생성자와 별도로 클래스의 인스턴스를 반환하는 정적 팩터리 메서드를 제공할 수 있습니다.

 

 

팩터리 메서드 장점

1. 이름을 가질 수 있다

`String.java의 valueOf
@Notnull @Contract(pure = true)
public static String valueOf(@NotNull char data[]){
    return new String(data);
}

`Main.java
//생성자를 사용하지 않고, 정적 팩토리 메서드를 통해 인스턴스 반환
public static void main(String[] args) {
    String str = String.valueOf("value assign");
    System.out.println(str);
}
위와 같이 정적 팩터리 메서드를 사용하면 이름을 가질 수 있다는 장점이 있습니다. 이름을 가지기 때문에 정확히 어떤 객체를 반환하는지 알 수 있습니다. 

 

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

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

`java.lang.Boolean.class
@jdk.internal.ValueBased
public final class Boolean implements java.io.Serializable, Comparable<Boolean>, Constable{

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

    @IntrinsicCandidate
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
}
대표적인 예로 Boolean.valueOf(boolean) 메서드는 객체를 아예 생성하지 않습니다. 그래서 같은 객체가 자주 요청되는 상황이라면 성능을 상당히 끌어올려 줍니다. 반복되는 요청에 같은 객체를 반환하는 식으로 언제 어느 인스턴스를 살아 있게 할지 통제할 수 있습니다. [= 인스턴스 통제 클래스]

 

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

반환할 객체의 클래스를 자유롭게 선택할 수 있게 하는 '엄청난 유연성'을 선물합니다. 구현 클래스를 공개하지 않고 객체를 반환할 수 있게 되어 API를 작게 유지할 수 있습니다. 이는 인터페이스를 정적 팩터리 메서드의 반환 타입으로 사용하는 인터페이스 기반 프레임워크를 만드는 핵심 기술입니다.

 

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

반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없습니다. OpenJDK에서는 원소의 수에 따라 두 가지 하위 클래스 중 하나의 인스턴스를 반환합니다. 원소가 64개 이하면 원소들을 long 변수 하나로 관리하는 RegularEnumSet의 인스턴스를, 65개 이상이면 long 배열로 관리하는 jumboEnumSet의 인스턴스를 반환합니다.

 

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

서비스 제공자 프레임워크를 만드는 근간이 되는 개념입니다. 서비스 제공자 프레임워크는 JDBC가 대표적인 예입니다. 여기서 제공자는 서비스의 구현체이고, 프레임워크는 구현체들을 클라이언트에게 제공하며 통제합니다. 그래서 클라이언트를 구현체로부터 분리해줍니다.

서비스 제공자 프레임워크의 3개의 핵심 컴포넌트가 있는데, 구현체의 동작을 정의하는 서비스 인터페이스, 제공자가 구현체를 등록할 때 사용하는 제공자 등록 API, 클라이언트가 서비스의 인스턴스를 얻을 때 사용하는 서비스 접근 API가 있습니다.

 

팩터리 메서드 단점

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

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

 

정적 팩터리 메서드 이름 명명 방식

from

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

of

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

valueOf

from과 of의 더 자세한 버전
예) BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

instance or getInstance

매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
예) StackWalker luke = StackWalker.getInstance(options);

create or newInstance

매번, 새로운 인스턴스를 생성해 반환함을 보장한다.
예) Object newArray = Array.newInstance(classObject, arrayLen);

getType

생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용한다. "Type"은 팩터리 메서드가 반환할 객체의 타입이다.
예) FileStore fs = Files.getFileStore(path)

newType

생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용한다. "Type"은 팩터리 메서드가 반환할 객체의 타입이다.
예) BufferedReader br = Files.newBufferedReader(path);

type

getType과 newType의 간결한 버전
예) List<Complaint> litany = Collections.list(legacyLitany);

 

마무리

저는 팩터리 메서드를 사용할 생각을 하지 않고 항상 public을 통해 생성자를 제공하였습니다. 
앞으로는 정적 팩터리 메서드와, public 생성자의 장단점을 분석하고
어떤 것이 더 유리한 지 판단하여 사용하겠습니다.

포스팅 읽어주셔서 감사합니다.

 

[출처]

이펙티브 자바 Effective Java 3/E - 조슈아 블로크 저/ 개앞맵시 

728x90
반응형