Backend 취업준비/Java
정적 팩토리 메서드
aammddkkzxc
2024. 2. 22. 22:42
- List.of(), Optional.of() 등의 메서드를 써보다가 of()라는 비슷한 이름을 가진 메서드가 있다는 것을 알게 되었다
- 이 것들은 정적 팩토리 메서드 라고 칭하는 것 들 이었다.
- 생성자에 private 접근제한을 두어 외부에서 new키워드를 이용하여 객체 생성을 막는 조치가 되어 있다.
정적 팩토리 메서드의 특징
1. 생성 목적에 대한 이름 표현이 가능하다
- 클래스를 설계할때 다양한 타입의 객체를 생성하기 위해, 목적에 따라 생성자 오버로딩을 하였다
- 오버로딩 된 생성자와 new키워드를 통해 객체를 생성하려면 개발자는 생성자의 인자 순서와 내부 구조를 알고 있어야 한다
- 생성자로 넘기는 매개변수 만으로는 반환될 객체의 특성을 제대로 표현하기 어렵다(생성자 이름이 클래스 이름으로 고정되어 있어야 하는 규칙)
- 적절한 네이밍으로 객체의 특성을 유추 할 수 있게 된다
2. 인스턴스에 대하여 관리하기 용이하다
- 메서드를 통하여 한단계 거쳐 객체를 생성하기 때문에 기본적으로 객체 생성 및 통제 관리를 할 수 있게 된다
- 필요에 따라 새로운 객체를 생성시키거나, 객체를 하나만 만들어 두고 이를 공유하여 불필요한 객체 생성을 막을 수도 있다.
- 싱글톤 디자인 패턴 => getInstance() 정적 팩토리 메서드로 오로지 하나의 객체만 반환하도록 한다
- 캐싱 구조 => 정적 초기화 블록 과 정적 팩토리 메서드로 캐싱 구조를 만들 수 있다. 자 주 사용되는 요소의 개수가 정해져있다면 해당 개수만큼 미리 생성해놓고 조회(캐싱)할 수 있는 구조
캐싱 예시1
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);
}
...
}
캐싱 예시 2
class Day {
private String day;
public Day(String day) { this.day = day; }
public String getDay() { return day; }
}
// Day 객체를 생성하고 관리하는 Flyweight 팩토리 클래스
class DayFactory {
// Day 객체를 저장하는 캐싱 저장소 역할
private static final Map<String, Day> cache = new HashMap<>();
// 자주 사용될것 같은 Day 객체 몇가지를 미리 등록한다
static {
cache.put("Monday", new Day("Monday"));
cache.put("Tuesday", new Day("Tuesday"));
cache.put("Wednesday", new Day("Wednesday"));
}
// 정적 팩토리 메서드 (인스턴스에 대해 철저한 관리)
public static Day from(String day) {
if(cache.containsKey(day)) {
// 캐시 되어있으면 그대로 가져와 반환
System.out.println("해당 요일은 캐싱되어 있습니다.");
return cache.get(day);
} else {
// 캐시 되어 있지 않으면 새로 생성하고 캐싱하고 반환
System.out.println("해당 요일은 캐싱되어 있지 않아 새로 생성하였습니다.");
Day d = new Day(day);
cache.put(day, d);
return d;
}
}
}
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. 객체 생성을 캡슐화 한다
- .웹 어플리케이션을 개발하다보면 계층 간에 데이터를 전송하기 위한 객체로 DTO(Data transfer object)를 정의해서 사용한다.
- DTO와 Entity간에는 자유롭게 형 변환이 가능해야 하는데, 정적 팩터리 메서드를 사용하면 내부 구현을 모르더라도 쉽게 변환할 수 있다.
- 만약 정적 팩토리 메서드를 쓰지 않고 DTO로 변환한다면 외부에 생성자의 내부 구현이 모두 드러난다
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);
///
Car carDto = CarDto.from(car); // 정적 팩토리 메서드를 쓴 경우
CarDto carDto = new CarDto(car.getName(), car.getPosition); // 생성자를 쓴 경우
정적 팩토리 메서드 네이밍 컨벤션
정적 팩토리 메서드에서의 네이밍은 단순히 선호도 의미를 넘어서 거의 법칙 정도로 사용되고 있다고 하니 꼭 익히자
- from : 하나의 매개 변수를 받아서 객체를 생성
- of : 여러개의 매개 변수를 받아서 객체를 생성
- getInstance | instance : 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
- newInstance | create : 새로운 인스턴스를 생성
- get[OtherType] : 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
- new[OtherType] : 다른 타입의 새로운 인스턴스를 생성.
열거형에서의 예시
public enum Size {
SMALL, MEDIUM, LARGE, EXTRA_LARGE;
public static Size of(String sizeName) {
for (Size size : values()) {
if (size.name().equalsIgnoreCase(sizeName)) {
return size;
}
}
throw new IllegalArgumentException("Invalid size name: " + sizeName);
}
}
출처
https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/