- 예시 코드
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
- 장점
1. 이름을 가질 수 있다.
생성자와 매개변수만으로는 리턴될 객체의 특성을 제대로 설명하기 어렵다.
예를들면
// 생성자
public class Person {
public Person() {}
}
// 정적 팩토리 메소드
public class Person {
private static Person PERSON = new Person();
private Person() {}
public static final Person getInstance() {
return PERSON;
}
}
정적 팩토리 메소드는 Person 클래스의 인스턴스를 가져온다는 의미의 getInstance()를 통해 의미를 확실히 할 수 있다.
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
static으로 선언했기 때문에 애플리케이션이 실행될 때 한번 생성하여 인스턴스가 유지되기 때문에 생성하지 않아도 된다.
생성 비용이 큰 객체가 반복적으로 요청될 때 성능을 향상 시킬 수 있다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있다.
이것은 반환할 객체의 클래스를 자유롭게 선택할 수 있는 유연성이다.
이를 통해 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있다.
인터페이스를 정작 팩토리 메소드의 반환 타입으로 사용하는 인터페이스 기반 프레임워크의 핵심 기술이다.
4. 입력 파라미터에 따라 매번 다른 클래스의 객체를 리턴할 수 있다.
반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
EnumSet 클래스는 public 생성자 없이 정적 팩토리만 제공하는데, 원소의 수에 따라 두 가지 하위 클래스 중 하나의 인스턴스를 리턴한다.
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);
}
사용자는 RegularEnumSet, JumboEnumSet 클래스를 몰라도 된다.
5. 정적 팩토리 메소드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
이는 서비스 제공자 프레임워크를 만드는 근간이 된다.
서비스 제공자 프레임워크는 서비스 구현체를 클라이언트에 제공하는 역할을 프레임워크가 통제하여, 클라이언트를 구현체로부터 분리해준다.
서비스 제공자 프레임워크의 예시로는 JDBC가 있다.
서비스 제공자 프레임워크는 3개의 핵심 컴포넌트로 이루어져있다.
1. 구현체의 동작을 정의하는 서비스 인터페이스
- JDBC의 Connection이 구현체의 동작을 정의하는 서비스 인터페이스
2. 제공자가 구현체를 등록할 때 사용하는 제공자 등록API
- DriverManager.registerDriver가 제공자 등록 API
3. 클라이언트가 서비스의 인스턴스를 얻을 때 사용하는 서비스 접근 API
- Driver가 서비스 제공자 인터페이스 역할
다른 예시로 의존 객체 주입 프레임워크나 브릿지 패턴이 있다.
결국 장점은 일반 생성자에 비해 확장성이 좋다는 것이다.
- 단점
1. 상속하려면 public이나 protected 생성자가 필요하기 때문에 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다.
예시로 Collection 프레임뤄크의 유틸리티 구현 클래스들은 상속할 수 없다.
하지만 상속보다 컴포지션을 사용하도록 유도하고, 불변 타입으로 만들도록 유도하므로 오히려 장점이 될 수도 있다.
2. 정적 팩토리 메소드는 개발자가 찾기 어렵다.
이는 API문서화와 메소드 이름도 규약에 따라 만들어서 최대한 친절함이 필요하다.
- from: 매개변수를 하나 받아서 해당 타입의 인스턴스를 리턴하는 형변환 메소드
- ex) Date date = Date.from(instant);
- of: 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메소드
- Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf: from과 of의 더 자세한 버전
- BigInteger prime = BigInterger.valueOf(Integer.MAX_VALUE);
- instance 혹은 getInstance: 매개변수로 명시한 인스턴스를 리턴하지만, 같은 인스턴스임을 보장하지는 않음
- StackWalker luke = StackWalker.getInstance(options);
- 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);
- type: getType과 newType의 간결한 버전
- List<Complaint> litany = Collections.list(legecyLitany);
- 정리
정적 팩토리 메소드와 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋음.
정적 팩토리 사용이 생성자보다 유리한 경우가 많기 때문에 무작정 생성자를 사용하던 습관이 있다면 고칠 것.
- 참고
'개발 > Java&Kotlin' 카테고리의 다른 글
[Java] private 생성자나 열거 타입으로 싱글턴 보증하기 (0) | 2023.01.07 |
---|---|
[Java] 생성자 대신 빌더 사용하기 (0) | 2023.01.05 |
[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (7) (0) | 2022.12.23 |
[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (6) (0) | 2022.12.20 |
[Spring] 스프링 배치(Spring Batch) 가이드 따라가기 (5) (0) | 2022.12.18 |