윤개발

[ITEM1] 생성자 대신 정적 팩토리 메소드를 고려하라. 본문

책 내용 요약/Effective Java 3판

[ITEM1] 생성자 대신 정적 팩토리 메소드를 고려하라.

DEV_SJ 2020. 2. 24. 20:10

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

클래스의 인스턴스를 얻는 전통적인 방법은 public 생성자다.
하지만 클래스는 생성자와 별도로 정적 팩토리 메서드를 제공할 수 있다.

생성자보다 정적 팩토리 메소드를 제공하는 방식에는 장단점이 있다.


장점

1.이름을 가질 수 있다.

생성자에게 넘기는 매개변수와 생성자로는 객체의 특성을 제대로 설명하지 못한다.

BigInteger(int,int,Random) // 생성자
BigInteger.probablePrime(int bitLength, Random rnd) //정적 팩토리 메소드

다음 두 코드 중에서 어느 쪽이 "소수인 BigInteger 인스턴스를 반환한다"는 의미를 더 잘 설명할까?

또한 매개변수의 순서를 다르게 하여 생성자를 추가하는 방식을 정적 팩토리 메소드로 바꾸고 차이점을 잘 들어내주는 이름을 지어준다면
개발자가 엉뚱한 것을 호출하는 실수를 방지할 수 있다.

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

인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 재활용하는 식으로 불필요한 객체 생성을 피한다.
대표적인 예로 아래 코드는 객체를 아예 생성하지 않는다.

Boolean.valueOf(boolean)

따라서 생성비용이 높고 자주 호출되는 객체를 정적팩토리 메소드로 구현한다면 성능을 올려준다.
플라이웨이트 패턴과 비슷한 기법이다.(플라이웨이트 패턴 - 객체의 내부에서 참조하는 객체를 직접 만드는 것이 아니라 없다면 생성하고, 만들어져 있다면 공유하는 식으로 객체를 구성하는 패턴)

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

API 를 작성할 때, 구현클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API 를 작게 유지할 수 있다.
즉 프로그래머는 명시한 인터페이스대로 동작하는 객체를 얻을 것임을 알기에 굳이 별도 문서를 찾아가며 구현클래스가 뭔지 알아보지 않아도 된다.

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

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

  • EnumSet 클래스는 원소가 64개 이하면 ReqularEnumSet을 65개 이상이면 JumboEnumSet의 인스턴스를 반환한다.

클라이언트는 팩터리가 건네주는 객체가 어느 클래스의 인스턴스인지 알 필요가 없다.
반환 하는 인스턴스가 EnumSet의 하위 클래스이기만 하면 되는 것이다.

5. 반환하는 시점에는 객체의 클래스가 존재하지 않아도 된다.

대표적인 서비스 제공자 프레임워크 JDBC가 있다. 클라이언트는 원하는 구현체의 조건을 명시(서비스 접근 API)할 수 있고 이러한 조건들이 유연한 정적팩토리의 실체이다.


단점

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

앞서 이야기한 쿨래스들은 상속할 수 없다는 이야기이다. 이 단점은 상속보다 컴포지션을 사용하도록 유도하도록 장점으로 받아들일 수도 있다.

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

정적 팩터리 메서드는 일반 메서드이므로 생성자처럼 Java docs에 명확히 표현되지 않는다.
따라서 생성자가 없으면 정적 팩터리 메서드를 찾는 등의 개발자의 불편함이 생기므로 알려진 규약에 따라 정적팩터리 메소드의 네이밍을 하는 식으로 문제를 완화해줘야 한다.

from: 매개변수를 받아서 해당 타입의 인스턴스를 반환

Date date = Date.from(instant);

of: 여러 매개변수를 받아서 인스턴스 반환

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

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

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

instance / getInstance: 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.

StackWalker luke = StackWalker.getInstance(options);

create / newInstance: 매번 새로운 인스턴스를 생성해 반환한다.

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

getType: getInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용

FileStore fs = Files.getFileStore(path);

newType: newInstance와 같으나 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용

BufferedReader br = Files.newBufferedReader(path);

type: getType과 newType의 간결한 버전

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

정리

정적 팩터리 메서드의 장단점을 이해하고 사용하는 것이 좋다.
정적팩터리를 사용하는게 유리한 경우가 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 고치자.

 

 

github wiki - https://github.com/JAVA-JIKIMI/EFFECTIVE-JAVA3/wiki/item-1-sungjaeyoon

Comments