Book Summary&Review/이펙티브 자바 (Effective Java 3E)

Item1. 생성자 대신 정적 팩터리 메서드를 고려하라

찹키리 2025. 1. 26. 23:31

 

💡정적 팩토리 메서드 (Static Factory Method)

new 키워드로 생성자를 직접 호출하지 않고 Static Method를 사용해 간접적으로 생성자를 호출해 객체를 생성하는 방식

 

public class Sfm {
    private int num;
    private String str;

    // 생성자를 private으로 선언해 외부에서 호출X
    private Sfm(int num, String str) {
        this.num = num;
        this.str = str;
    }

    // 정적 팩토리 메서드, private으로 선언된 생성자를 호출해 인스턴스 생성
    public static Sfm create(int num, String str) {
        return new Sfm(num, str);
    }
}

 

public static void main(String[] args) {
    int createNum = 99;
    String createStr = "create Sfm instance";
    
    // 정적 팩토리 메서드를 호출해 Sfm 클래스의 인스턴스 생성
    Sfm sfmInstance = Sfm.create(createNum, createStr);
}

 

 

👍 장점

1. 이름을 가질 수 있다.

  • 함수명으로 인스턴스 생성 목적을 구분할 수 있음

[EX.생성자]

  • 생성자는 클래스명과 동일해야 하기 때문에 이름으로 생성 목적을 나타낼 수 없음
  • 목적이 다른 생성자를 매개변수로만 구분하게 되면 생성자가 많아질 수록 구분하기 어려워짐
public class Book {
  private String title;
  private int status;
  private String discontinuedYearMn;

  public Book(String title, int status) {
    book.title = title;
    book.status = status;
  }

  public Book(String title, String discontinuedYearMn) {
    book.title = title;
    book.status = -1;
    book.discontinuedYearMn = discontinuedYearMn;
  }

 

[EX.정적팩토리메서드]

  • 신규 도서 생성과 단종 도서 생성이라는 인스턴스 생성 목적을 메서드 이름으로 구분할 수 있음
public class Book {
  private String title;
  private int status;
  private String discontinuedYearMn;

  private Book() {
  }

  // 도서 생성
  public static createBook(String title, int status) {
    Book book = new Book();
    book.title = title;
    book.status = status;
    
    return book;
  }
  
  // 단종 도서 생성
  public static createDiscontinuedBook(String title, String discontinuedYearMn) {
    Book book = new Book();
    book.title = title;
    book.status = -1;
    book.discontinuedYearMn = discontinuedYearMn;

    return book;
  }
}

 

 

 

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

👆 불변 클래스(Immutable Class)

  • 인스턴스의 내부 값을 수정할 수 없는, 가변적이지 않은 클래스
  • 대표적으로 String, Boolean, Integer, Float, Long 등의 래퍼 클래스가 있음
  • 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱해 재활용하는 식으로 불필요한 객체 생성을 피할 수 있음 [EX.Boolean.valueOf(boolean) : 객체를 아예 생성하지 않는 메서드]

 

 

👆 플라이웨이트 패턴(Flyweight Pattern)

  • 재사용 가능한 객체 인스턴스를 공유해 메모리 사용량을 최소화하는 패턴
  • 자주 변하는 속성과 변하지 않는 속성을 구분해 변하지 않는 속성을 캐시해 재사용
  • 정적 팩토리 메서드의 경우 (특히, 생성 비용이 큰) 같은 객체가 자주 요청되는 상황에서 성능 개선 효과
    → 플라이웨이트 패턴 (Flyweight Pattern)이 이와 비슷한 기법

👆 인스턴스 통제 클래스(Instance-Controlled Class)

  • 반복되는 요청에 같은 객체를 반환하는 식으로 언제 어느 인스턴스를 살아 있게 할지 통제가 가능
    인스턴스 통제(instance-controlled) 클래스 (EX. Enum)
  • 인스턴스를 통제하면 클래스를 싱글턴이나 인스턴스화 불가(noninstantiable)로 만들 수도 있음
  • 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐 임을 보장할 수 있음


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

  • 반환할 객체의 클래스를 자유롭게 선택할 수 있음 (유연성)
    → 구현 클래스를 공개하지 않고도 해당 객체를 반환할 수 있음
  • 인터페이스 기반 프레임워크를 만드는 핵심 기술
    → 인터페이스를 정적 팩토리 메서드의 반환 타입으로 사용

[EX.java.util.Collections - 인스턴스화 불가 클래스 (default 생성자를 private으로 선언)]

 

 

 

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

  • 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체든 반환 가능
  • 대표적인 예로 EnumSet클래스
    → 원소가 64개 이하면 RegularEnumSet 인스턴스를, 65개 이상이면 JunboEnumSet인스턴스 반환
    → 클라이언트는 두 하위 클래스의 존재를 알 필요가 없음

[EX.EnumSet]

 

 

 

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

  • 서비스 제공자 프레임워크(Service Provider Framework)를 만드는 근간 → EX. JDBC
  • 서비스 제공자 프레임워크
    • 서비스 인터페이스 (Service Interface) : 구현체의 동작을 정의 EX. Connection
    • 제공자 등록 API (Provider Registration API) : 제공자가 구현체를 등록할 때 사용 EX. DriverManager.registerDriver
    • 서비스 접근 API (Service Access API) : 클라이언트가 서비스의 인스턴스를 얻을 때 사용 EX. DriverManager.getConnection
    • 서비스 제공자 인터페이스(Service Provider Interface) : 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체를 설명 → 해당 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 함 EX. Driver
  • 클라이언트는 서비스 접근 API를 호출할 때 원하는 구현체의 조건을 명시, 조건을 명시하지 않는 경우 기본 구현체 혹은 지원하는 구현체를 돌아가면서 반환
    → ‘유연한 정적 팩토리’의 실체



👎 단점

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

  • 상속보다 컴포지션을 사용하도록 유도하고, 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점으로 작용할 수 있음



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

  • 생성자처럼 API 설명에 명확히 드러나지 않기 때문에 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 찾아내야 함
  • API 문서를 잘 작성하고, 메서드 이름을 널리 알려진 규약에 따라 작성하는 식으로 문제 완화 필요
    • 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/getInstance
      - 매개변수로 명시한 인스턴스를 반환하나, 같은 인스턴스임을 보장하지는 않음
      ✔️ StackWalker luke = StackWalker.getInstance(options);
    • create/newInstance
      - instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장
      ✔️ Object newArray = Array.newInstance(classObject, arrayLen);
    • getType
      - getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 사용
      - “Type”은 팩토리 메서드가 반환할 객체의 타입
      ✔️ FileStore fs = Files.getFileStore(path)
    • newType
      - newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 떄 사용
      - “Type”은 팩토리 메서드가 반환할 객체의 타입
      ✔️ BufferedReader br = Files.newBufferedReader(path);
    • type
      - getType과 newType의 간결한 버전
      ✔️ List<Complaint> litany = Collections.list(legacyLitany);