이 글은 다음을 참고하여 작성되었습니다. 감사합니다. 🙇🏻♂️
GoF 디자인 패턴 중 팩토리 패턴에서 유래한 이 단어는 객체를 생성하는 역할을 분리하겠다는 취지가 담겨있다. (팩토리 패턴, 팩토리 클래스에 대해 더 알고 싶다면?)
-> 정적 팩토리 메서드란 객체 생성의 역할을 하는 클래스 메서드라는 의미로 요약해볼 수 있다.
정적 팩토리 메서드가 무엇인지 감이 안 온다면 예시를 통해 알아볼 수 있다.
다음 코드는 java.time 패키지에 포함된 LocalTime 클래스의 정적 팩토리 메서드이다.
// LocalTime.class
...
public static LocalTime of(int hour, int minute) {
ChronoField.HOUR_OF_DAY.checkValidValue((long)hour);
if (minute == 0) {
return HOURS[hour];
} else {
ChronoField.MINUTE_OF_HOUR.checkValidValue((long)minute);
return new LocalTime(hour, minute, 0, 0);
}
}
...
// hour, minutes을 인자로 받아서 9시 30분을 의미하는 LocalTime 객체를 반환한다.
LocalTime openTime = LocalTime.of(9, 30);
위 예시 코드에서 본 LocalTime 클래스의 of 메서드처럼 직접적으로 생성자를 통해 객체를 생성하는 것이 아닌 메서드를 통해서 객체를 생성하는 것을 정적 팩토리 메서드라고 한다.
또 다른 예시로 enum의 요소를 조회할 때 사용하는 valueOf 도 정적 팩토리 메서드의 일종이라고 할 수 있다.
미리 생성된 객체를 “조회”를 하는 메서드이기 때문에 팩토리의 역할을 한다고 볼 수는 없지만, 외부에서 원하는 객체를 반환해주고 있으므로 결과적으로는 정적 팩토리 메서드라고 간주해도 좋다고 한다.
public enum Color {
RED,
BLUE;
}
...
Color redColor = Color.valueOf("RED");
Color blueColor = Color.valueOf("BLUE");
정적 팩토리 메서드가 무엇이고, 어떤 역할을 하는지 감을 잡았다면 다음과 같은 의문이 들어야 정상이라고 한다.
Q : 객체를 생성하는 역할은 자바에서 제공하는 “생성자”가 하는데, 왜 정적 팩토리 메서드를 따로 만들어서 객체를 생성하는 것일까?
생성자와는 어떤 차이가 있나?
A : “생성자 대신 정적 팩토리 메서드를 고려하라”
- 조슈아 블로크 in “이펙티브 자바”
벌써부터 정적 팩토리 메서드를 사용하는 것이 더 장점이 클것이라는 느낌이 오지 않냐고 묻는다.. 생성자와 정적 팩토리 메서드는 객체를 생성한다는 같은 역할을 하고 있지만 그 활용도는 엄연히 차이가 나기 때문이다. 실제로 정적 팩토리 메서드가 어떤 면에서 생성자보다 우위를 차지하고 있는지 하나씩 검증해보자.
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);
}
...
}
createAutoLotto와 createMenualLotto 모두 로또 객체를 생성하고 반환하는 정적 팩토리 메서드이다. 메서드의 이름만 보아도 로또 객체를 자동으로 생성하는지, 아니면 수동으로 생성하는지 단번에 이해할 수 있을 것이라고 한다.
이처럼 정적 팩토리 메서드를 사용하면 해당 생성의 목적을 이름에 표현할 수 있어 가독성이 좋아지는 효과가 생긴다.
enum과 같이 자주 사용되는 요소의 개수가 정해져있다면 해당 개수만큼 미리 생성해놓고 조회(캐싱)할 수 있는 구조로 만들수 있다.
정적 팩터리 메서드와 캐싱구조를 함께 사용하면 매번 새로운 객체를 생성할 필요가 없어진다.
1부터 45까지의 로또 번호를 enum으로도 만들 수 있지만 LottoNumber 클래스 안에서 반복문을 통해 쉽게 45개의 인스턴스를 만들 수 있으므로 후자의 방법을 사용했다.
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으로 설정함으로써 객체 생성을 정적 팩토리 메서드로만 가능하도록 제한할 수 있다는 것이다. 이를 통해 정해진 범위를 벗어나는 로또 번호의 생성을 막을 수 있다는 장점을 확보할 수 있게 된다.
하위 자료형 객체를 반환하는 정적 팩토리 메서드의 특징은 상속을 사용할 때 확인할 수 있다. 이는 생성자의 역할을 하는 정적 팩토리 메서드가 반환값을 가지고 있기 때문에 가능한 특징이다.
public class Level {
...
public static Level of(int score) {
if (score < 50) {
return new Basic();
} else if (score < 80) {
return new Intermediate();
} else {
return new Advanced();
}
}
...
}
정적 팩토리 메서드는 객체 생성을 캡슐화하는 방법이기도 하다.
- 캡슐화(Encapsulization)란?
본래 캡슐화의 의미는 데이터의 은닉이지만,
여기서는 생성자를 클래스의 메서드 안으로 숨기면서 내부 상태를 외부에 드러낼 필요없이 객체 생성 인터페이스 단순화 시킬 수 있음을 의미한다.
웹 어플리케이션을 개발하다보면 계층 간에 데이터를 전송하기 위한 객체로 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); // 생성자를 쓴 경우
확실한 점은 정적 팩토리 메서드를 적절히 사용했을 때 얻을 수 있는 장점이 더 많다는 것이다. 객체간 형 변환이 필요하거나, 여러 번의 객체 생성이 필요한 경우라면 생성자보다는 정적 팩토리 메서드를 사용해보자.
다음은 정적 팩토리 메서드의 용도와 역할에 대해 이해도를 높이는데 도움이 되고
일반적으로 많이 사용되는 네이밍 컨벤션이라고 한다.
정적 팩토리 메서드 네이밍 컨벤션
- from : 하나의 매개 변수를 받아서 객체를 생성
- of : 여러개의 매개 변수를 받아서 객체를 생성
- getInstance | instance : 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
- newInstance | create : 새로운 인스턴스를 생성
- get[OtherType] : 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
- new[OtherType] : 다른 타입의 새로운 인스턴스를 생성.