EffectiveJava3 Item1
공부하는 내용을 정리하는 목적으로 작성하고 있습니다. 잘못 작성된 내용을 지적해주시면 좀더깊이 공부해서 내용을 수정하겠습니다.
Item1. 생성자 대신 정적 팩터리 메서드를 고려하라
생성자 대신 정적 팩터리 메서드를 Client에 제공할 경우 장점
1. 이름을 가질 수 있다.
생성자 이름과 생성자에 넘기는 매개변수로는 반환될 객체의 특성을 정확하게 설명하기 어렵다.
정적 팩터리 메서드는 메서드 이름만 명확하면 객체의 특성을 잘 묘사할 수 있다.
ex) BigInteger.probablePrime
또한 하나의 시그니처로 하나의 생성자만 만들 수 있다.
입력 매개변수를 다르게하여 생성자를 추가하는 방식은 Client가 어떤 생성자를 선택해야하는지 어렵게 만들 수 있다.
정적 팩터리 매서드는 필요한 매개변수에 따라 이름을 다르게 지어 명확하게 Client에게 제공할 수 있다.
2. 호출될 때 마다 인스턴스를 새로 생성하지 않아도 된다.
이로인해 불변클래스는 인스턴스를 미리 만들어놓거나, 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
객체 생성비용이 큰 경우가 자주 호출된다면 이것은 성능상 이익을 볼 수 있다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
반환할 객체의 클래스를 자유롭게 선택할 수 있는 유연성이 생긴다.
예를들어 public API를 제공할 때 내부 구현 클래스를 숨기고 인터페이스만 공개하여 API의 크기를 줄일 수 있다.
Client는 명시된 인터페이스대로 동작할 객체를 알고있으므로 내부 구현체가 무엇인지 상세하게 알아볼 필요가 없어진다.
Java8 이전에는 인터페이스에 정적 메서드를 둘 수 없었기때문에 인스턴스화 될 수 없는 동반 클래스를 만들어 제공하는 방식을 사용했다.
ex) Collections
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
반환 타입의 하위 클래스이기만 하면 어떤 클래스의 객체를 반환해도 상관없다.
API가 발행된 이후 요구사항 변경에 따라 다른 클래스를 구현하게 되면 새 클래스를 반환하도록 릴리즈하는것이 가능하다.
(내부 구현은 숨겨져 있으므로 Client는 반환될 객체의 Type이 무엇인지 중요하지 내부 구현체가 무엇인지는 중요하지 않다.)
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
JDBC와 같은 서비스 제공자 프레임워크를 개발 할 때 유용해진다.
서비스 제공자 프레임워크는 서비스 인터페이스, 제공자 등록 API, 서비스 접근 API 3가지로 구성될 수 있는데 정적 팩터리 메서드가 서비스 접근 API의 역할을 수행한다.
서비스 접근 API를 작성할 때 내부 구현체가 정해지지 않더라도 반환할 서비스 인터페이스만 정의하여 제공할 수 있다.
예를들어 Repository 인터페이스를 개발하고있는데 데이터를 Memory에 저장할지, RDB에 저장할지, File에 저장할지 정해지지 않았어도
Client에게 이 API가 Repository 인터페이스를 반환한다는 것만 정의하면 내부 구현체는 추후 개발해도 문제가 없다.
정적 팩터리 메서드를 사용할 때의 단점
1. 상속을 하려면 public, protected 생성자가 필요한데 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
예를들어 Collections와 같은 유틸리티 구현체 클래스들은 하위 클래스를 가질 수 없다.
하지만 이것은 클래스를 불변으로 만들 수 있고, 상속보다 컴포지션 패턴을 사용하도록 유도함으로써 장점으로 활용할 수 도 있다.
2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
생성자를 통해 객체를 생성하지않고 직접 필요한 메서드를 찾아야하는 수고가 필요하다.
문서화를 최대한 잘해서 Client가 최대한 빠르고 쉽게 메서드를 찾을 수 있도록 해야한다.
대표적인 팩터리 메서드 명명 규칙
from
매개변수를 하나 받아서 해당 타입의 인스턴스를 반환
Date.from(instant);
of 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환
EnumSet.of(A, B, C);
valueOf
from과 of의 좀 더 자세한 버전
BigInteger.valueOf(Integer.MAX_VALUE);
instance or getInstance
매개변수로 명시한 인스턴스를 반환하지만 같은 인스턴스임을 보장하지는 않는다.
StackWalker.getInstance(options);
create or newInstance
getInstance와 같지만 매번 새로운 인스턴스를 반환한다.
Array.newInstance(classObject, arrayLen);
getType
getInstance와 같지만 생성할 클래스가 아닌 다른 클래스에 정의할 때 사용하며, Type은 반환할 객체의 타입이다.
Files.getFileStore(path);
newType
netInstance와 같지만 생성할 클래스가 아닌 다른 클래스에 정의할 때 사용하며, Type은 반환할 객체의 타입이다.
Files.newBufferedReader(path);
type
getType과 newType의 간결한 버전
Collections.list(legancyLitany);