: 객체의 생성을 담당하는 클래스 메서드
: Gof 디자인 패턴 중 팩토리 패턴에서 유래
사실상 new를 직접적으로 사용하지 않을 뿐, 정적 팩토리 메서드라는 클래스 내에 선언되어있는 메서드를 내부의 new를 이용해 객체를 생성해서 반환하는 것
즉. 정적 팩토리 메소드를 통해서 new를 간접적으로 사용한다
정적 팩토리 메서드가 생성자 생성 방법과 다른 5가지
1. 이름을 가질 수 있다
2. 호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다
5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다
public class LottoFactory() {
private static final int LOTTO_SIZE = 6;
private static List<LottoNumber> allLottoNumbers = ...; // 1~45까지의 로또 넘버
public static Lotto createAutoLotto() {
Collections.shuffle(allLottoNumbers);
return new Lotto(allLottoNumbers.stream()
.limit(LOTTO_SIZE)
.collect(Collectors.toList()));
}
public static Lotto createManualLotto(List<LottoNumber> lottoNumbers) {
return new Lotto(lottoNumbers);
}
...
}
호출할 때 마다 새로운 객체를 생성할 필요가 없다
대표적인 예가 enum 타입이다
사용되는 값의 개수가 정해져 있으면 해당 값을 미리 생성해 놓고 조회할 수 있는 구조로 만들 수 있다
정적 팩토리 메서드와 캐싱 구조를 함께 사용하면 객체를 매번 새롭게 만들어 줄 필요가 없다
하위 자료형 객체를 반환할 수 있다
: 상속을 사용할 때 확인 가능
정적 팩토리 메서드가 반환할 때 상황에 따라 하위 클래스 타입의 객체 반환 가능
예제)
public class Grade {
...
private static Grade of(int takenSemester) {
if (0 < takenSemester && takenSemester <= 2) {
return new Freshman();
}
if (2 < takenSemester && takenSemester <= 4) {
return new Sophomore();
}
if (4 < takenSemester && takenSemester <= 6) {
return new Junior();
}
if (6 < takenSemester &&<takenSemester <= 8){
return new Senior();
}
...
}
...
of () 라는 정적 팩토리 메서드를 사용하여 객체 생성 시, 수강 학기에 따라 해당하는 학년 객체를 반환
, 분기처리를 통해 하위 타입의 객체를 반환할 수 있다는 것을 알 수 있다.
public class LottoNumber {
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;
private static Map<Integer, LottoNumber> lottoNumberCache = new HashMap<>();
static {
IntStream.range(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
.forEach(i -> lottoNumberCache.put(i, new LottoNumber(i)));
}
private int number;
private LottoNumber(int number) {
this.number = number;
}
public LottoNumber of(int number) { // LottoNumber를 반환하는 정적 팩토리 메서드
return lottoNumberCache.get(number);
}
...
}
👉 미리 생성된 로또 번호 객체의 캐싱을 통해서 새로운 객체 생성의 부담을 덜 수 있다는 장점도 있지만, 생성자의 접근 제한자를 private으로 설정함으로써 객체 생성을 정적 팩토리 메서드로만 가능하도록 제한
할 수 있다는 것
이를 통해 정해진 범위를 벗어나는 로또 번호의 생성을 막을 수 있다는 장점을 확보
반환 타입의 하위 타입이기만하면 어느 타입이든 객체를 반환해도 상관없다는 것
만약 특정 인터페이스를 상속받은 구현체들이 있을 때 객체 생성시 상황에 따라 유동적으로 해당하는 구현체 타입으로 반환이 가능하다고 생각하면 된다
EnumSet의 정적 팩토리 메서드로 파라미터로 받은 값의 길이에 따라 RegularEnumSet()이나 JumboEnumSet을 반환(입력 매개변수에 따라 다른 클래스의 객체를 반환)
웹 어플리케이션을 개발하다보면 계층간의 데이터를 전송하기 위한 객체로 DTO(data transfer object)를 정의해서 사용한다
DTO와 Entity간에는 자유롭게 형변환이 가능해야하는데 정적 팩토리 메서드를 사용하면
내부 구현을 모르더라도 쉽게 변환할 수 있다
public class CarDto {
private String name;
private int position;
pulbic static CarDto from(Car car) {
return new CarDto(car.getName(), car.getPosition());
}
}
// Car -> CatDto 로 변환
CarDto carDto = CarDto.from(car);
👉 만약 정적 팩토리 메서드를 쓰기 않고 DTO로 변환하면,
외부에서 생성자의 내부 구현을 모두 드러낸 채 해야한다
Car carDto = CarDto.from(car); // 정적 팩토리 메서드를 쓴 경우
CarDto carDto = new CarDto(car.getName(), car.getPosition); // 생성자를 쓴 경우
정적 팩토리 메서드는 단순히 생성자의 역할을 대신하는 것 뿐만 아니라
우리가 좀 더 가독성 좋은 코드를 작성하고 객체지향적으로 프로그래밍을 할 수 있도록 도와준다
⭐️ 도메인에서 객체 생성의 역할 자체가 중요한 경우에는 따로 클래스를 분리하는 것이 좋다
factory.class
하지만 이는 암묵적으로 사용하는 정적 팩터리 메서드 컨벤션과, API 문서 작성을 잘 하면 해결 할 수 있다고 생각한다.