💡정적 팩토리 메서드 (Static Factory Method)
new 키워드로 생성자를 직접 호출하지 않고 Static Method를 사용해 간접적으로 생성자를 호출해 객체를 생성하는 방식
public class Sfm {
private int num;
private String str;
// 생성자를 private으로 선언해 외부에서 호출X
private Sfm(int num, String str) {
this.num = num;
this.str = str;
}
// 정적 팩토리 메서드, private으로 선언된 생성자를 호출해 인스턴스 생성
public static Sfm create(int num, String str) {
return new Sfm(num, str);
}
}
public static void main(String[] args) {
int createNum = 99;
String createStr = "create Sfm instance";
// 정적 팩토리 메서드를 호출해 Sfm 클래스의 인스턴스 생성
Sfm sfmInstance = Sfm.create(createNum, createStr);
}
👍 장점
1. 이름을 가질 수 있다.
- 함수명으로 인스턴스 생성 목적을 구분할 수 있음
[EX.생성자]
- 생성자는 클래스명과 동일해야 하기 때문에 이름으로 생성 목적을 나타낼 수 없음
- 목적이 다른 생성자를 매개변수로만 구분하게 되면 생성자가 많아질 수록 구분하기 어려워짐
public class Book {
private String title;
private int status;
private String discontinuedYearMn;
public Book(String title, int status) {
book.title = title;
book.status = status;
}
public Book(String title, String discontinuedYearMn) {
book.title = title;
book.status = -1;
book.discontinuedYearMn = discontinuedYearMn;
}
[EX.정적팩토리메서드]
- 신규 도서 생성과 단종 도서 생성이라는 인스턴스 생성 목적을 메서드 이름으로 구분할 수 있음
public class Book {
private String title;
private int status;
private String discontinuedYearMn;
private Book() {
}
// 도서 생성
public static createBook(String title, int status) {
Book book = new Book();
book.title = title;
book.status = status;
return book;
}
// 단종 도서 생성
public static createDiscontinuedBook(String title, String discontinuedYearMn) {
Book book = new Book();
book.title = title;
book.status = -1;
book.discontinuedYearMn = discontinuedYearMn;
return book;
}
}
2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
👆 불변 클래스(Immutable Class)
- 인스턴스의 내부 값을 수정할 수 없는, 가변적이지 않은 클래스
- 대표적으로 String, Boolean, Integer, Float, Long 등의 래퍼 클래스가 있음
- 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱해 재활용하는 식으로 불필요한 객체 생성을 피할 수 있음 [EX.Boolean.valueOf(boolean) : 객체를 아예 생성하지 않는 메서드]
👆 플라이웨이트 패턴(Flyweight Pattern)
- 재사용 가능한 객체 인스턴스를 공유해 메모리 사용량을 최소화하는 패턴
- 자주 변하는 속성과 변하지 않는 속성을 구분해 변하지 않는 속성을 캐시해 재사용
- 정적 팩토리 메서드의 경우 (특히, 생성 비용이 큰) 같은 객체가 자주 요청되는 상황에서 성능 개선 효과
→ 플라이웨이트 패턴 (Flyweight Pattern)이 이와 비슷한 기법
👆 인스턴스 통제 클래스(Instance-Controlled Class)
- 반복되는 요청에 같은 객체를 반환하는 식으로 언제 어느 인스턴스를 살아 있게 할지 통제가 가능
→ 인스턴스 통제(instance-controlled) 클래스 (EX. Enum) - 인스턴스를 통제하면 클래스를 싱글턴이나 인스턴스화 불가(noninstantiable)로 만들 수도 있음
- 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐 임을 보장할 수 있음
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
- 반환할 객체의 클래스를 자유롭게 선택할 수 있음 (유연성)
→ 구현 클래스를 공개하지 않고도 해당 객체를 반환할 수 있음 - 인터페이스 기반 프레임워크를 만드는 핵심 기술
→ 인터페이스를 정적 팩토리 메서드의 반환 타입으로 사용
[EX.java.util.Collections - 인스턴스화 불가 클래스 (default 생성자를 private으로 선언)]
4. 입력 매개변수(파라미터)에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체든 반환 가능
- 대표적인 예로 EnumSet클래스
→ 원소가 64개 이하면 RegularEnumSet 인스턴스를, 65개 이상이면 JunboEnumSet인스턴스 반환
→ 클라이언트는 두 하위 클래스의 존재를 알 필요가 없음
[EX.EnumSet]
5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
- 서비스 제공자 프레임워크(Service Provider Framework)를 만드는 근간 → EX. JDBC
- 서비스 제공자 프레임워크
- 서비스 인터페이스 (Service Interface) : 구현체의 동작을 정의 EX. Connection
- 제공자 등록 API (Provider Registration API) : 제공자가 구현체를 등록할 때 사용 EX. DriverManager.registerDriver
- 서비스 접근 API (Service Access API) : 클라이언트가 서비스의 인스턴스를 얻을 때 사용 EX. DriverManager.getConnection
- 서비스 제공자 인터페이스(Service Provider Interface) : 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체를 설명 → 해당 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 함 EX. Driver
- 클라이언트는 서비스 접근 API를 호출할 때 원하는 구현체의 조건을 명시, 조건을 명시하지 않는 경우 기본 구현체 혹은 지원하는 구현체를 돌아가면서 반환
→ ‘유연한 정적 팩토리’의 실체
👎 단점
1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
- 상속보다 컴포지션을 사용하도록 유도하고, 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점으로 작용할 수 있음
2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
- 생성자처럼 API 설명에 명확히 드러나지 않기 때문에 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 찾아내야 함
- API 문서를 잘 작성하고, 메서드 이름을 널리 알려진 규약에 따라 작성하는 식으로 문제 완화 필요
- from
- 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
✔️ Date d = Date.from(instant); - of
- 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
✔️ Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING); - valueOf
- from과 of의 더 자세한 버전
✔️ BigInteger prime = BigInteger.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(legacyLitany);
- from