아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라

soluinoon·2023년 8월 30일
0

Effective Java

목록 보기
1/2
post-thumbnail
post-custom-banner

이 글은 Effective Java 3판을 읽고 정리한 글 입니다!

정적 팩터리 메서드


정적 팩터리 메서드의 대표적 예시인 LocalDate.of 입니다.
정적(static)으로 클래스의 인스턴스를 반환하기 때문에 정적 팩터리 메서드라 부릅니다.
생성자도 있는데, 왜 이런 메서드를 사용할까요?

정적 팩터리 메서드의 장점

1. 이름을 가질 수 있다.

책의 예제에서 다루는 BigInteger.probablePrime의 예를 보겠습니다.

BigInteger.probablePrime는 주어진 비트로 만들 수 있는 수 중에 랜덤한 소수를 반환하는 정적 팩터리 메서드 입니다.
만약 일반적인 public BigInteger(int bigLength, Random rnd) 생성자라면 무엇을 생성하는지 유추할 수 없을 것 입니다.

하지만, probablePrime처럼 메서드 명으로 어떤 특성을 갖는지 설명할 수 있으면 사용하는 사람도 편하고, 유지보수하는 사람도 편하겠죠?

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

인스턴스를 미리 만들어놓거나, 새로 생성한 인스턴스를 캐싱하여 재활용하는 방식으로 사용될 수 있습니다.
가장 대표적인 예시로 싱글톤 패턴이 있습니다.

public class Singleton {
	// 1. 최초에 한번만 만들어 놓음
    private static Singleton instance = new Singleton();
    
    private Singleton() {
        // 생성자는 외부에서 호출못하게 private 으로 지정해야 한다.
    }
	// 정적 팩터리 메서드로 미리 만들어놨던 1번의 인스턴스를 반환
    public static Singleton getInstance() {
        return instance;
    }

이렇게 언제, 어느 인스턴스를 살아 있게 할지를 정할 수 있습니다. 이런 클래스를 인스턴스 통제 클래스라고 부릅니다.

이런 특징은 저희가 자주 사용하는 Enum 클래스의 근간이 됩니다.


Enum 생성자 위에 javadoc을 번역하면...

단독 프로그래머가 이 생성자를 호출할 수 없습니다. 이것은 열거형 선언에 대한 응답으로 컴파일러에 의해 사용된 코드에 의해 사용된다.

🚨 emit에 대한 의미가 애매해서 의역했습니다. emit에 대한 답변은 스택오버플로우 링크에서 참고해주세요.
대강 프로시저 코드에서 ~를 사용해서 만든 무언가란 의미인 것 같습니다.

생성자를 protected로 만든 뒤, valueOf로 값을 얻게 만들고 있습니다.
인스턴스화 할 수 없는 Enum의 특징을 잘 보여주고 있습니다.

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

책에서는 저희가 많이 사용하는 Collections 클래스의 예제를 들고 있습니다.

컬렉션 프레임워크는 수정 불가, 동기화 지원 등 45개의 유틸리티 클래스 구현체가 있다고 합니다.
이렇게 많은 구현체를 Collections에서 정적 팩토리 메서드로 반환하고 있습니다.
이렇게 반환한 클래스들은 개발자들이 구체 클래스를 상세하게 뜯어 공부할 수고를 줄여줍니다. 명시한 인터페이스의 하위 타입 이므로, 인터페이스만으로 조작이 가능하기 때문입니다.

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


EnumSet은 원소가 64개 이하면 원소들을 long 변수 하나로 관리하는 RegularEnumSet의 인스턴스를, 65개 이상이면 long배열로 관리하는 JumboEnumSet의 인스턴스를 반환합니다.


이처럼 입력 변수에 따라 하위타입의 인스턴스를 반환할 수 있습니다.

만약 원소가 0개인 EnumSet을 따로 처리하고 싶어서 EmptyEnumSet 클래스를 따로 만든다고 하면, 그냥 상속받고 정적 팩터리 메서드에 길이가 0 이면 EmptyEnumSet을 반환한다는 조건하나만 추가하면 됩니다.

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

이 부분은 이해가 더 필요할 것 같아서, JDBC 파트를 따로 작성해 연구해보겠습니다.

정적 팩터리 메서드의 단점

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



생성자를 private으로 막고, 정적 팩터리 메서드만을 통해 생성을 하도록 만든다면 상위 클래스를 생성할 수 없기 때문에 상속이 불가능합니다.
하지만!! 이 부분은 불변 타입으로 만들기 위해선 오히려 장점으로 작용할 수도 있습니다.

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

이 부분은 IntelliJ를 사용한다면 크게 와닿지 않는 단점이라 생각합니다(개인적인 의견입니다.)
조금 불편한건 기본적인 생성자는 단축키로 3초안에 만들 수 있지만, 정적 팩터리 메서드는 직접 이름을 지어줘서 만들어줘야 하기 때문에 생각과 고민이 필요한 점이 있겠습니다.

책에서 추천하는 정적 팩터리 메서드 명명 방식

  • from
    매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드

    ex) Date d = Date.from(instant);

  • of
    여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드

    ex) Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);

  • valueOf
    from과 of의 더 자세한 버전

    ex) BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

  • instance, getInstance
    매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않음.

    ex) StackWalker luke = StackWalker.getInstance(options);

  • create, newInstance
    instance, getInstance와 비슷하나, 매번 새로운 인스턴스를 생성해 반환

    ex) Object newArray = Array.newInstance(classObject, arrayLen);

  • getType
    생성할 클래스가 아닌 다른 클래스의 팩터리 메서드를 정의할 때 쓴다.

    ex) FileStore fs = Files.getFileStore(path)

  • newType
    생성할 클래스가 아닌 다른 클래스의 팩터리 메서드를 정의할 때 쓴다. 항상 새로운 인스턴스를 생성해 반환한다.

    ex) BufferedReader br = Files.newBufferedReader(path);

  • type
    좀 더 간결한 버전

    ex) List<Complaint> litany = Collections.list(legacyLitany);

마치며

읽고 채화하는데 오래걸리지만, 정말 재밌어요 ㅎㅎ
여러분들도 꼭 읽어보셨으면 좋겠습니다.

Reference

이펙티브 자바 3판

profile
수박개 입니다.
post-custom-banner

0개의 댓글