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

DEINGVELOP·2023년 6월 21일
0

클래스를 얻는 수단

  1. public 생성자 : 클래스의 인스턴스를 얻는 전통적인 수단
  2. 정적 팩토리 메서드 : 클래스의 인스턴스를 반환하는 정적 메서드
    public static Boolean valueOf(boolean b) {
    	return b ? Boolean.TRUE : Boolean.FALSE;
    }

정적 팩터리 메서드

장점

1. 이름을 가질 수 있다.

  • 이름을 통해 반환될 객체의 특성을 쉽게 묘사할 수 있다.
    • BigInteger(int, int, Random) vs BigInteger.probablePrime() (값이 소수인 BigInteger를 반환하는 메서드)
  • 생성자를 여러 개 만들 때, 시그니처가 같은 생성자가 여러 개 필요할 경우 → 생성자를 정적 팩터리 메서드로 바꾸고 각각의 차이를 잘 드러내는 이름을 짓는다.

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

  • 불변 클래스 : 인스턴스를 미리 만들어놓거나 새로 생성한 인스턴스를 캐싱하여 재사용할 수 있음
    • Boolean.valueOf(boolean) : 객체를 아예 생성하지 않음
  • 생성 비용이 큰 같은 객체가 자주 요청되는 상황에서 성능을 끌어올려줌 (≒ 플라이웨이트 패턴(Flyweight pattern))

    Flyweight pattern

    • 어떤 클래스의 인스턴스 한 개만 가지고 여러 개의 "가상 인스턴스"를 제공하고 싶을 때 사용하는 패턴
    • 즉 인스턴스를 가능한 대로 공유시켜 쓸데없이 new연산자를 통한 메모리 낭비를 줄이는 방식
  • 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있음 => instance-controlled class(인스턴스 통제 클래스)
    • 클래스를 싱글턴으로도, 혹은 인스턴스화 불가 클래스로도 만들 수 있다.
    • 불변값 클래스에서, 동치인 인스턴스가 단 하나뿐임을 보장할 수 있다.
    • 플라이웨이트 패턴의 근간이 됨
  • 열거 타입 : 인스턴스가 하나만 만들어지는 것을 보장함

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

  • 반환 객체의 클래스를 자유롭게 선택할 수 있음 -> 유연성 ↑!!!
  • 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있음 -> API를 작게 유지할 수 있음
    • 인터페이스를 정적 팩터리 메서드의 반환 타입으로 사용하는 인터페이스 기반 프레임워크를 만드는 핵심 기술
    • ~ Java 7 : 인터페이스에 정적 메서드를 선언할 수 없었음 → 이름이 "Type"인 인터페이스를 반환하는 정적 메서드가 필요할 경우, "Types"라는 companion class(동반 클래스)를 만들어 그 안에 정의하는 것이 관례였음
      • Java Collection Framework 중 수정 불가, 동기화 등의 45개 유틸리티 구현체 중 대부분 -> java.util.Collections에서 정적 팩털히 메서드를 통해 얻도록 했음
      • 정적 팩터리 메서드를 사용하는 클라이언트 : 얻은 객체를 구현 클래스가 아닌 인터페이스만으로 다루게 됨 → 좋은 습관
    • Java 8 : 인터페이스도 정적 메서드를 가지게 됨 → 인스턴스화 불가 동반 클래스를 둘 이유가 별로 없음 (인터페이스 자체에 두면 되기 때문)
      - 단, 정적 메서드를 구현한 코드 중 많은 부분은 package-private 클래스에 두어야 함
      - Java 9 : private 정적 메서드까지 허락하지만, 정적 필드와 정적 멤버 클래스는 public이어야 함

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

  • 반환 타입의 하위 타입이기만 하면, 어떤 클래스의 객체를 반환해도 상관 없음

    • EnumSet : 원소가 64개 이하면 long 변수 1개를 관리하는 RegularEnumSet, 원소가 65개 이상이면 long 배열로 관리하는 JumboEnumSet의 인스턴스 반환
  • 클라이언트는 팩터리가 건네주는 객체가 어느 클래스의 인스턴스인지 알 수도 없고 알 필요도 없음

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

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

    • provider = 서비스의 구현체
    • 구현체들을 클라이언트에 제공하는 역할을 프레임워크가 통제하며, 클라이언트를 구현체로부터 분리해줌
  • 3개의 핵심 컴포넌트로 이루어짐

    • service interface (서비스 인터페이스)
    • provider registration API (제공자 등록 API)
    • service access API (서비스 접근 API)
      • 클라이언트가 사용할 때, 원하는 구현체의 조건을 명시할 수 있음 (else, 기본 구현체 반환 or 지원하는 구현체들을 하나씩 돌아가며 반환함)
      • 이것이 바로 서비스 제공자 프레임워크의 근간인 유연한 정적 팩터리
    • service provider interface (서비스 제공자 인터페이스)
      • 네번째 컴포넌트
      • 서비스 인터페이스의 인스턴스를 생성하는 팩터리 객체 설명해줌
      • 이게 없으면, 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 함
  • ex) JDBC(Java Database Connectivity)

    • Connection : 서비스 인터페이스
    • DriverManager.registerDriver : 제공자 등록 API
    • DriverManager.getConnection : 서비스 접근 API
    • Driver : 서비스 제공자 인터페이스
  • 패턴의 변형이 여러 가지 있음

    • Bridge Pattern(브릿지 패턴) : 공급자가 제공하는 것보다 더 풍부한 서비스 인터페이스를 클라이언트에 반환할 수 있음
    • Dependency Injection(의존 객체 주입) Framework : 강력한 서비스 제공자

단점

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

  • 컬렉션 프레임워크의 유틸리티 구현 클래스들은 상속할 수 없다.
  • 단, 상속보다는 컴포지션을 사용하도록 유도하고, 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점이 될 수 있다.

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

  • 생성자처럼 API 설명에 명확히 드러나지 않음 -> 사용자가 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 찾아야 함
  • 따라서 API 문서를 잘 써놓고, 메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 완화해줘야 함

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

  • from :매개변수를 하나 받아서 해당 타입의 인스턴스 반환하는 형변환 메서드
  • of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
  • valueOf : from과 of의 더 자세한 버전
  • instance, getInstance : (받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않음
  • create, newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장
  • getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용함 (Type : 팩터리 메서드가 반환할 객체의 타입)
  • newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스의 팩터리 메서드를 정의할 때 사용함 (Type : 팩터리 메서드가 반환할 객체의 타입)
  • type : getType, newType의 간결한 버전

정리

  • 정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니, 상대적인 장단점을 이해하고 사용하는 것이 좋다.
  • 단, 정적 팩터리를 사용하는 것이 유리한 경우가 더욱 많으므로, 무작정 public 생성자를 제공하던 습관이 있다면 고쳐야 한다.

0개의 댓글