본문 바로가기

자바☕/이펙티브 자바

이펙티브 자바 읽고 정리해보기 1.

728x90

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

클래스의 인스턴스를 얻을 때 일반적으로 public 생성자를 활용한다. 이 때 정적 팩터리 메서드를 활용하는 방법도 있다

 

예시)

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

기본 타입인 boolean 값을 받아 Boolean 객체 참조로 값을 반환한다

 

정적 팩터리 메서드 방식의 장점

1. 이름을 가질 수 있다

생성자는 그 자체로는 어떤 이유로 생성하는 지, 무엇이 반환되는 지를 제대로 설명할 수 없다. 정적 팩터리 메서드는 이를 잘 설명할 수 있다

하나의 메서드 시그니처로는 하나의 생성자를 만들 수 있기 때문에, 생성자만을 사용해 인스턴스를 여러 개 얻는 방식은 파라미터를 추가하는 방법밖에 없으므로, 혼동할 가능성이 있다

그러므로, 한 클래스에 시그니처가 같은 여러 생성자가 필요한 경우, 정적 팩터리 메서드를 고려하는 것이 좋다

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

불변 클래스나 생성 비용이 큰 클래스의 인스턴스를 얻기 위해 매번 새로 생성할 필요가 없다. 항상 동일한 메서드를 통해 주소가 같은 객체를 반환하기 때문에 인스턴스를 통제할 수 있다(인스턴스 통제 클래스라고도 한다)

인스턴스를 통제하면 클래스를 싱글턴으로 만들 수도 있고, 인스턴스화 불가로 만들 수도 있다. 또한 불변 클래스의 인스턴스 주소를 보장할 수 있다.

인스턴스 통제는 플라이웨이트 패턴의 근간이 되며 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다

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

API를 만들 때 유연하게 유지할 수 있다. 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있고, 이를 통해 인터페이스 기반 프레임워크를 만들 수 있다. 대표적인 사례로 자바 컬렉션 프레임워크가 있다. 프로그래머는 인터페이스대로 동작하는 객체를 얻을 수 있어 실제 구현 클래스를 찾아볼 필요가 없어지고, 클라이언트도 얻은 객체를 인터페이스만으로 다루게 된다.

다만 자바 9에서도 인터페이스에는 public 정적 멤버와 private 정적 메서드를 허용하지만, 여전히 정적 필드와 정적 멤버 클래스는 public으로 선언해야 한다

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

반환 타입의 하위 타입이라면 어떤 클래스의 객체를 반환하던지 상관없다. EnumSet 클래스는 public 생성자 없이 정적 팩터리 메서드만 제공하는데, OpenJDK에서는 원소 수에 따라 두 가지 하위 클래스 중 하나의 인스턴스를 반환한다. 원소가 64개 이하인 경우 원소들을 long 변수 하나로 관리하는 RegularEnumSet의 인스턴스를, 65개 이상이면 long 배열로 관리하는 JumboEnumSet의 인스턴스를 반환한다

클라이언트는 하위 클래스의 존재와 변경 사실 등에 대해 알 수도 없고 알 필요도 없다.

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

서비스 제공자 프레임워크를 만드는 근간이 되는 장점으로, 대표적인 사례로 JDBC가 있다. 이 프레임워크에서 제공자는 서비스의 구현체이다. 그리고 프레임워크가 다수의 구현체들을 통제하는 역할을 하여 클라이언트와 구현체를 분리한다

서비스 제공자 프레임워크는 구현체의 동작을 정의하는 서비스 인터페이스, 제공자가 구현체를 등록할 때 사용하는 제공자 등록 API, 클라이언트가 서비스의 인스턴스를 얻을 때 사용하는 서비스 접근 API로 총 3개의 핵심 컴포넌트로 구성된다. 클라이언트는 서비스 접근 API를 사용할 때 원하는 구현체의 조건을 명시할 수 있고, 조건을 명시하지 않으면 기본 구현체를 반환하거나 지원하는 구현체들을 하나씩 돌아가며 반환한다. 이 서비스 접근 API가 서비스 제공자 프레임워크의 핵심이다. 종종 서비스 제공자 인터페이스라는 컴포넌트를 사용하기도 하는데, 서비스 인터페이스의 인스턴스를 생성하는 팩터리 객체를 설명한다. 서비스 제공자 인터페이스가 없다면, 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 한다.JDBC에서는 Connection이 서비스 인터페이스, DriverManager.registerDriver가 제공자 등록 API, DriverManager.getConnection이 서비스 접근 API 역할을, Driver가 서비스 제공자 인터페이스 역할을 수행한다

서비스 접근 API는 공급자보다 더 풍부한 서비스 인터페이스를 클라이언트에게 반환할 수 있다. 브리지 패턴, 의존 주입 프레임워크를 예시로 들 수 있으며, 자바 6부터는 java.util.serviceLoader라는 범용 서비스 제공자 프레임워크가 제공되어 프레임워크를 직접 만들 필요가 거의 없다(JDBC는 자바 6이전이라 serviceLoader를 사용하지 않는다)

 

단점

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

~ 컬렉션 프레임워크의 구현 클래스는 상속할 수 없다는 뜻이다. 그래서 상속보다 컴포지션을 사용하도록 유도하고 있으며, 불변타입으로 만드려면 이 제약을 지켜야 하기 때문에 보기에 따라 장점일 수도 있다

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

생성자처럼 API 설명에 명확히 드러나지 않으니 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 찾아야 한다. 다음으로는 일반적으로 사용하는 규약에 따라 정적 팩터리 메서드에 사용하는 이름들이다.

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

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

- valueOf : from과 of의 더 자세한 버전

- instance 또는 getInstance : (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다

- create 또는 newInstance : 매번 새로운 인스턴스를 생성해 반환하는 것을 보장한다

- getType : getInstance와 같지만, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. "Type"은 팩터리 메서드가 반환할 객체의 타입이다

ex) FileStrore fs = Files.getFileStore(path);

- newType : newInstance와 같지만, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. "Type"은 팩터리 메서드가 반환할 객체의 타입이다

- type : getType과 newType의 간결한 버전

 

*핵심 정리

- 정적 팩터리 메서드와 public 생성자는 각 쓰임새가 있으므로 장단점을 이해하고 쓰는 것이 좋다. 다만 정적 팩터리 메서드를 사용하는 방식이 더 좋은 경우가 많으므로 고려해야 한다

728x90