최근 객체지향에 대해 공부하면서 많은 사람들의 코드를 보며 눈으로 배우는 재미를 느끼고 있다.
그 중 정적 팩토리 메서드라는 내용에 대해 알아보았다.
정적 팩토리 메서드란?
여기서 팩토리라는 용어는 GoF 디자인 패턴 중 팩토리 패턴에서 유래한 이 단어는 객체를 생성하는 역할을 분리하겠다는 취지가 담겨있다.
즉, 객체 생성의 역할을 하는 클래스 메서드로 요약할 수 있다.
예시를 들어보자면
예시1
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);
위 코드는 java.time 패키지에 포함된 LocalTime 클래스의 정적 팩토리 메서드이다.
위 예시 코드에서 of 메서드처럼 직접적으로 생성자를 통해 객체를 생성하는 것이 아닌 메서드를 통해서 객체를 생성하는 것을 말한다.
또 다른 예시를 들어보자면
예시2
public enum Color {
RED,
BLUE;
}
...
Color redColor = Color.valueOf("RED");
Color blueColor = Color.valueOf("BLUE");
enum의 요소를 조회할 때 사용하는 valueOf도 정적 팩토리 메서드의 일종이라고 할 수 있다.
미리 생성된 객체를 "조회"하는 메서드이기 때문에 팩토리의 역할을 한다고 볼 수 없지만, 외부에서 원하는 객체를 반환해주고 있으므로 결과적으로는 정적 팩토리 메서드라고 간주해도 좋다.
그렇다면 다음과 같은 의문이 들 수 있다.
객체를 생성하는 역할은 자바에서 제공하는 "생성자"가 하는데, 왜 정적 팩토리 메서드를 따로 만들어서 객체를 생성하는 것일까?
생성자와 어떤 차이가 있는가? 에 대해 "이펙티브 자바"라는 책에서 다음과 같이 소개한다.
생성자 대신 정적 팩토리 메서드를 고려하라
무슨 장점을 가지고 있는지 알아보자.
1. 이름을 가질 수 있다.
객체는 생성 목적과 과정에 따라 생성자를 구별해서 사용할 필요가 있다. new라는 키워드를 통해 객체를 생성하는 생성자는 내부 구조를 잘 알고 있어야 목적에 맞게 객체를 생성할 수 있다.
하지만 정적 팩토리 메서드를 사용하면 메서드 이름에 객체의 생성 목적을 담아낼 수 있다.
다음 자동로또와 수동로또를 생성하는 팩토리 클래스의 일부코드를 살펴보자.
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 와 createManualLotto 모두 로또 객체를 생성하고 반환하는 정적 팩토리 메서드이다.
메서드의 이름만 보아도 로또 객체를 자동으로 생성하는지, 아니면 수동으로 생성하는지 단번에 이해 할 수 있다.
이처럼 정적 팩토리 메서드를 사용하면 해당 생성의 목적을 이름에 표현할 수 있어 가독성이 좋아지는 효과가 있다.
2. 호출할 때마다 새로운 객체를 생성할 필요가 없다.
1부터 45까지의 로또 번호를 enum으로도 만들 수 있지만 LottoNumber 클래스 안에서 반복문을 통해 쉽게 45개의 인스턴스를 만들 수 있으므로 후자의 방법을 사용했다.
여기서 짚고 넘어가야할 점은 미리 생성된 로또 번호 객체의 캐싱을 통해서 새로운 객체 생성의 부담을 덜 수 있다는 장점도 있지만, 생성자의 접근 제한자를 private으로 설정함으로써 객체 생성을 정적 팩토리 메서드로만 가능하도록 제한할 수 있다는 것이다. 이를 통해 정해진 범위를 벗어나는 로또 번호의 생성을 막을 수 있다는 장점을 확보할 수 있다.
3. 하위 자료형 객체를 반환할 수 있다.
하위 자료형 객체를 반환하는 정적 팩토리 메서드의 특징은 상속을 사용할 때 확인할 수 있다. 이는 생성자의 역할을 하는 정적 팩토리 메서드가 반환값을 가지고 있기 때문에 가능한 특징이다.
Basic, Intermediate, Advanced 클래스가 Level라는 상위 타입을 상속받고 있는 구조를 생각해보자. 시험 점수에 따라 결정되는하위 등급 타입을 반환하는 정적 팩토리 메서드를 만들면, 다음과 같이 분기처리를 통해 하위 타입의 객체를 반환할 수 있다.
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();
}
}
...
}
4. 객체 생성을 캡슐화할 수 있다.
캡슐화란?
데이터의 은닉을 말한다. 여기서는 클래스의 메서드 안으로 숨기면서 내부 상태를 외부에 드러낼 필요없이 객체 생성 인터페이스를 단순화 시킬 수 있다.
정적 팩토리 메서드 네이밍 컨벤션
- from : 하나의 매개 변수를 받아서 객체를 생성
- of : 여러개의 매개 변수를 받아서 객체를 생성
- getInstance | instance : 인스턴스를 생성, 이전에 반환했던 것과 같을 수 있음.
- newInstance | create : 새로운 인스턴스를 생성
- get[OtherType] : 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
- new[OtherType] : 다른 타입의 새로운 인스턴스를 생성.
'😀Language > Java' 카테고리의 다른 글
[Test] contains를 이용한 테스트 (0) | 2024.10.27 |
---|---|
Arrays.asList() 와 List.of() 정리 (0) | 2024.10.17 |
Optional<T> isPresent(), ifPresent(), orElseThrow (0) | 2024.03.14 |
[Optional이란?] (0) | 2024.03.14 |
[람다와 스트림] (0) | 2024.03.14 |