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

2023. 11. 8. 23:39Book/이펙티브 자바

객체 생성과 파괴

 - 배울 점

 1. 객체를 만들어야 할 때와 만들지 말아야 할 때를 구분하는 법

 2. 올바른 객체 생성 방법과 불필요한 생성을 피하는 방법

 3. 제때 파괴됨을 보장하고 파괴 전에 수행해야 할 정리 작업을 관리

 

 

 

 

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

public static Boolean valueOf(boolean b) {
	return b ? Boolean.TRUE : Boolean.FALSE
}

 

 

 클래스는 클라이언트에 public 생성자 대신 (Or 함꼐)  정적 팩토리 메서드를 제공할 수 있다.

 - 장점 : 5

 - 단점 : 2

 

 

 장점 1. 이름을 가질 수 있다.

 - 입력 매개변수의 순서 바꿔 시그니처가 같은  생성자를 만들 수 있지만 안좋은 발상!

 => 각 생성자의 역할을 구분하기 힘들다.

 

 장점 2. 인스턴스 생성을 통제할 수 있다.

 - 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.

 - 생성 비용이 큰 객체가 자주 요청되는 상황에 아주 좋다.(ex => new Int[1000000] 외 메모리,디스크 사용량,대역폭)

 - 이유 : 인스턴스를 통제하면 싱글턴으로 만들 수도, 인스턴스화 불가로 만들 수도 있다. 또한 불변 값 클래스에서 동치인  인스턴스가 단 하나임을 보장할 수 있다. 플라이웨이트 패턴의 근간이 되며, 인스턴스가 하나만 만들어짐을 보장한다.

 

 

장점 3, 4, 5, 공통 예시

    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }

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

 

 - API를 만들 때  이 유연성을 응용하면 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있다. 이는 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 하다.

 

 

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

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

 

 - 클라이언트는 객체가 어느 클래스의 인스턴스인지 알 필요도 없고 알 수도 없다. EnumSet의 하위 클래스이기만 하면 된다.

 

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

 -

//StoreService 인터페이스를 구현한 MilkStoreService 라는 클래스가 없을 때
StoreService storeService = new MilkStoreService();
 //=> MildStoreService 클래스가 없으므로 컴파일 에러 발생


//StoreService 인터페이스를 구현한 MilkStoreService 라는 클래스가 없을 때
StoreService storeService = StoreService.of("Milk");

 //=> 컴파일 에러는 나지 않고 런타임 중 필요한 시점에 정적 팩토리 메서드 내에서 찾을 수 있으면 된다

 

 - 이런 유연함은 *서비스 제공자 프레임워크(service provider framework)를 만드는 근간이 된다. (JDBC)

 

 *이펙티브 자바에서는 서비스 제공자 프레임워크는 4개의 핵심 컴포넌트로 이뤄졌다고 한다.

  - 서비스 인터페이스 : 구현체 동작 정의

  - 제공자 등록 API : 구현체를 등록할 때 사용

  - 서비스 접근 API : 클라이언트가 서비스의 인스턴스를 얻을 때 사용

  - 서비스 제공자 인터페이스 : 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체를 설명

 

in JDBC

 1.Connection => 서비스 인터페이스

 2.DriverManager.registerDriver => 제공자 등록 API

 3.DriverManager.getConnection => 서비스 접근 API

 4.Driver => 서비스 제공자 인터페이스

 

 

 단점 1. 생성자 메서드의 접근지시자가 private 일 경우 상속 x

 단점 2. 정적 팩토리 메서드는 개발자가 찾기 어렵다.

 - 자바doc 에서 생성자 메서드와 달리 일반 메서드는 찾기 어렵다.

 - 그래서 메서드 이름을 지을 때 규약을 따라 짓는 식으로 문제를 완화해줘야 한다.

 

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

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

//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);