[스프링 부트] 16. DTO 리팩터링 by Lambda, Optional, Stream API

KangHo Lee's avatar
Nov 21, 2024
[스프링 부트] 16. DTO 리팩터링 by Lambda, Optional, Stream API
💡
Lambda, Optional, Stream API 모두 Java 1.8 (Java 8)에 새로 추가된 기능입니다.

1. 원본 DTO 코드

@Data public class OrderDetailDTO { private int orderId; private List<OrderProductDTO> products = new ArrayList<>(); private int sumPrice; public OrderDetailDTO(List<OrderOption> options) { // 1. orderId this.orderId = options.get(0).getOrder().getId(); // 2. sumPrice for (OrderOption option : options) { this.sumPrice += option.getTotalPrice(); } // 3. products Set<Integer> ids = new HashSet<>(); for (OrderOption option : options) { ids.add(option.getProduct().getId()); } // 3.2 중복된 상품의 크기만큼 반복하면서 주문 옵션 추가하기 for (Integer id : ids) { // 임시 컬렉션 List<OrderOption> temp = new ArrayList<>(); for (OrderOption option : options) { if (id == option.getProduct().getId()) { temp.add(option); } } OrderProductDTO product = new OrderProductDTO(temp); // this.products로 안 적어도 된다. products 와 같은 이름의 변수가 없어서 products.add(product); } } @Data class OrderProductDTO { private int productId; private List<OrderOptionDTO> options = new ArrayList<>(); public OrderProductDTO(List<OrderOption> options) { this.productId = options.get(0).getProduct().getId(); this.options = options.stream() .map(orderOption -> new OrderOptionDTO(orderOption)) .toList(); } } @Data class OrderOptionDTO { private int id; private String optionName; private int qty; private int totalPrice; public OrderOptionDTO(OrderOption option) { this.id = option.getId(); this.optionName = option.getOptionName(); this.qty = option.getQty(); this.totalPrice = option.getTotalPrice(); } } } }

2. sumPrice 리팩터링

for (OrderOption option : options) { this.sumPrice += option.getTotalPrice(); } // 리팩터링 후 this.sumPrice = options.stream() .mapToInt(value -> value.getTotalPrice()) .sum();
  • .mapToInt(value -> value.getTotalPrice())
    • 스트림의 각 Option 객체를 int 값으로 변환해서 sum() 메서드를 실행 가능하게 변환합니다.

3. products 리팩터링

Set<Integer> ids = new HashSet<>(); for (OrderOption option : options) { ids.add(option.getProduct().getId()); } for (Integer id : ids) { List<OrderOption> temp = new ArrayList<>(); for (OrderOption option : options) { if (id == option.getProduct().getId()) { temp.add(option); } } OrderProductDTO product = new OrderProductDTO(temp); products.add(product); } // 리팩터링 후 this.products = options.stream() .collect(Collectors.groupingBy(op -> op.getProduct().getId())) .entrySet() // Map타입을 Set타입으로 변경(컬렉션만 stream 가능) .stream() .map(entry -> new OrderProductDTO(entry.getValue())) // 2바퀴 .toList();
(1) options.stream()
  • options 리스트를 스트림으로 변환합니다.
(2) collect(Collectors.groupingBy(op -> op.getProduct().getId()))
  • groupingBy는 Collectors에서 제공하는 메서드로, 스트림 데이터를 그룹화합니다.
  • 여기서 op -> op.getProduct().getId()는 각 OrderOption 객체에서 Product의 id를 추출하는 람다식입니다.
  • 결과적으로, options 리스트가 Product ID를 기준으로 그룹화되어 아래와 같은 형태의 Map이 생성됩니다:
Map<Integer, List<OrderOption>>
(3) .entrySet()
  • Map의 데이터를 Set 형태로 변환합니다.
  • entrySet()은 Map의 모든 키-값 쌍을 반환합니다.
  • 결과적으로, Set<Map.Entry<Integer, List<OrderOption>>> 형태가 됩니다.
(4) .stream()
  • entrySet() 결과를 다시 스트림으로 변환합니다.
  • 이렇게 하면 키-값 쌍 각각에 대해 후속 작업을 스트림 방식으로 처리할 수 있습니다.
(5) .map(entry -> new OrderProductDTO(entry.getValue()))
  • 각 Map.Entry 객체를 처리하여 OrderProductDTO 객체로 변환합니다:
  • entry.getValue()는 List<OrderOption> 값을 반환합니다.
  • 이 값을 OrderProductDTO의 생성자에 전달하여 새로운 객체를 생성합니다.
  • 즉, 그룹화된 각 Product ID에 대해 하나의 OrderProductDTO 객체가 만들어집니다.
(6) .toList()
  • 스트림에서 생성된 모든 OrderProductDTO 객체를 List로 변환합니다.
  • 최종적으로, 이 리스트는 products 필드에 저장됩니다.
💡
전체 동작 요약
  1. options 리스트를 Product ID로 그룹화하여 Map 생성.
  1. 각 Product ID 그룹에 대해 OrderProductDTO 객체 생성.
  1. 결과를 List<OrderProductDTO>로 변환하여 products에 저장.
 
Share article

devleekangho