[리팩토링] if 문 없이 객체 만들어보자 - 팩토리 패턴, 상속관계 엔티티 생성
이 게시글은 pjh3749님의 게시글을 참고하였습니다.
📌 문제 상황
프로젝트를 하던 중 .. 상속관계에 있는 엔티티들을 매번 빌더로 만들어야하는 상황이었다.
방법이 없어 보여 switch 문 사용해서 썼지만,
프로젝트 팀원분이 조금 더 깔끔하게 만들면 보자 해서 리팩토링을 하기로 했다.
📌 기존 코드
부모 클래스 : itinerary
자식 클래스 : Movement, Lodgement, Stay
부모 클래스의 type 필드에 따라 자식들을 구분해서 객체를 만들어야하는 상황.
참고로 밑의 코드는 만든 객체들을 jpa를 이용해 db에 저장하는 상황이다.
for (ItineraryRequest ir : itineraryRequests) {
switch (ir.getType()) {
case MOVEMENT:
itinerary = itineraryRepository.save(
Movement.builder().trip(trip).itineraryName(ir.getMovementName())
.itineraryOrder(ir.getOrder()).departureDate(ir.getStartDate())
.arrivalDate(ir.getEndDate())
.departurePlace(ir.getDeparturePlace())
.arrivalPlace(ir.getArrivalPlace()).transportation(ir.getItem())
.build()
);
itineraryResponse = MovementResponse.fromEntity((Movement) itinerary);
break;
case LODGEMENT:
itinerary = itineraryRepository.save(
Lodgement.builder().trip(trip).itineraryName(ir.getItem())
.itineraryOrder(ir.getOrder()).checkIn(ir.getStartDate())
.checkOut(ir.getEndDate()).build()
);
itineraryResponse = LodgementResponse.fromEntity((Lodgement) itinerary);
break;
case STAY:
itinerary = itineraryRepository.save(
Stay.builder().trip(trip).itineraryName(ir.getItem())
.itineraryOrder(ir.getOrder()).departureDate(ir.getStartDate())
.arrivalDate(ir.getEndDate()).build()
);
itineraryResponse = StayResponse.fromEntity((Stay) itinerary);
break;
}
itineraryResponseList.add(itineraryResponse);
}
위의 코드를 보면 딱 느끼겠지만, 정말 지저분하다
자식 클래스에는 많은 필드들이 따로 존재하기 때문에, 빌더 패턴을 이용해 인스턴스를 생성해줄 때 많은 양의 코드를 요구한다.
그래서 팩토리 패턴을 활용하여 정리를 해보았다.
📌 팩토리 패턴?
팩토리 패턴은 객체를 생성하는 인터페이스를 미리 정의하지만,
인스턴스를 만들 클래스의 결정은 서브 클래스 쪽에서 결정하는 패턴이다.
팩토리 클래스란 들어오는 인자에 따라서 하나의 자식클래스의 인스턴스를 반환해주는 역할을 하는 클래스이다.
📝 팩토리 패턴의 장점
조건에 따른 객체의 생성을 서브 클래스에게 위임함으로써 객체간의 결합도가 낮아질 수 있다.
그에 따라 코드가 훨씬 깔끔해지고 유지보수가 쉬워진다.
📝 다음과 같을 때 활용
- if문을 많이 써서 객체를 생성해야할 때 (나같은 상황)
- 어떤 조건문을 써서 객체를 생성해야할지 모를때
- 객체 생성 책임을 딴 클래스에게 미루고 싶을 때
⭐️ Function을 활용하자 (함수형 인터페이스)
📝 Function은 함수형 인터페이스를 구현할 수 있는 라이브러리로 java.util.function에 있다.
java8부터 사용 가능하다.
매개변수가 1개일 때에는 Function<T, R>을,
매개변수가 2개일 때에는 BiFunction<T, U, R>을,
매개변수가 int형일 때에는 intFunction<R>을 사용하면 된다.
✔️ 나같은 경우, trip 필드와 type 필드를 통해 객체를 생성해야했으므로 BiFunction을 사용했다.
👩🏻💻 구현 과정
1. 일단 itineraryFactory를 만든다.
2. BiFunction을 담는 HashMap을 선언한다. private로 하고, 반환할 수 있는 메소드를 따로 만들 것이다.
public class ItineraryFactory {
private final static Map<ItineraryType, BiFunction<Trip,
ItineraryRequest, Itinerary>> map = new HashMap<>();
3. 다음과 같이 값을 put 해주자. 이 때 빌더 패턴을 사용한 객체를 생성할 수 있는 함수형 인터페이스를 만드는 것이다.
static {
map.put(ItineraryType.MOVEMENT, (trip, ir) ->
Movement.builder()
.trip(trip).itineraryName(ir.getMovementName()).itineraryOrder(ir.getOrder())
.itineraryType(ir.getType())
.departureDate(ir.getStartDate()).arrivalDate(ir.getEndDate())
.departurePlace(ir.getDeparturePlace()).arrivalPlace(ir.getArrivalPlace())
.transportation(ir.getItem())
.build()
);
map.put(ItineraryType.LODGEMENT, (trip, ir) ->
Lodgement.builder()
.trip(trip).itineraryName(ir.getItem())
.itineraryOrder(ir.getOrder()).itineraryType(ir.getType())
.checkIn(ir.getStartDate()).checkOut(ir.getEndDate())
.build()
);
map.put(ItineraryType.STAY, (trip, ir) ->
Stay.builder()
.trip(trip).itineraryName(ir.getItem())
.itineraryOrder(ir.getOrder()).itineraryType(ir.getType())
.departureDate(ir.getStartDate()).arrivalDate(ir.getEndDate())
.build()
);
}
4. 생성된 객체를 반환해줄 수 있는 메소드를 만들자. 객체 생성 메소드는 public으로 선언한다.
public static Itinerary getItineraryEntity(Trip trip, ItineraryRequest ir) {
BiFunction<Trip, ItineraryRequest, Itinerary> function = map.get(ir.getType());
return function.apply(trip, ir);
}
5. 다음과 같이 사용할 수 있다. 위의 기존코드와 비교했을 때 훨씬 로직이 깔끔해졌다. 😃 (약 30개의 줄 -> 6개의 줄)
for (ItineraryRequest ir : itineraryRequests) {
Itinerary itinerary = itineraryRepository.save(
ItineraryFactory.getItineraryEntity(trip, ir)
);
itineraryResponseList.add(
ItineraryResponseFactory.getItineraryResponse(itinerary)
);
}
🙋🏻♀️ 전체 코드
/**
* request의 여정 타입이 들어오면 그에 따라 entity를 반환해주는
* 팩토리 패턴을 적용한 클래스
*/
public class ItineraryFactory {
private final static Map<ItineraryType, BiFunction<Trip,
ItineraryRequest, Itinerary>> map = new HashMap<>();
static {
map.put(ItineraryType.MOVEMENT, (trip, ir) ->
Movement.builder()
.trip(trip).itineraryName(ir.getMovementName()).itineraryOrder(ir.getOrder())
.itineraryType(ir.getType())
.departureDate(ir.getStartDate()).arrivalDate(ir.getEndDate())
.departurePlace(ir.getDeparturePlace()).arrivalPlace(ir.getArrivalPlace())
.transportation(ir.getItem())
.build()
);
map.put(ItineraryType.LODGEMENT, (trip, ir) ->
Lodgement.builder()
.trip(trip).itineraryName(ir.getItem())
.itineraryOrder(ir.getOrder()).itineraryType(ir.getType())
.checkIn(ir.getStartDate()).checkOut(ir.getEndDate())
.build()
);
map.put(ItineraryType.STAY, (trip, ir) ->
Stay.builder()
.trip(trip).itineraryName(ir.getItem())
.itineraryOrder(ir.getOrder()).itineraryType(ir.getType())
.departureDate(ir.getStartDate()).arrivalDate(ir.getEndDate())
.build()
);
}
public static Itinerary getItineraryEntity(Trip trip, ItineraryRequest ir) {
BiFunction<Trip, ItineraryRequest, Itinerary> function = map.get(ir.getType());
return function.apply(trip, ir);
}
}