ShoppingMall Project

[Shop Project] 팩토리 메소드 패턴(Factory Method Pattern)을 활용한 주문 방식 분리

sagecode 2025. 5. 26. 20:35

현재 상황 및 문제 정의

쇼핑몰 프로젝트를 함에 있어 유저가 장바구니에 담아서 결제하는 경우, 상품 상세페이지 칸에서 직접 주문하는 경우 2가지로 방식을 나눌 수 있다. 모든 주문 방식을 하나로 처리하게 되면, 의존성이 커지게 된다. 또한, 결제 페이지까지 가게되면 공통된 부분들이 많으므로 팩토리 메소드 패턴을 이용해서 주문을 구현할 수 있다.

  • 장바구니 기본 주문
  • 즉시결제에 따른 개별 주문

 

Factory Method Pattern을 이용한 주문 방식 구현

주문 방식에 맞는 Factory 구현

public interface OrderFactory {
    Order createOrder(Customer customer, List<Product> products);
}

public class CartOrderFactory implements OrderFactory {
    @Override
    public Order createOrder(Customer customer, Map<Product, Integer> productMap) {
        Order order = new Order(customer, OrderStatus.ORDERED);
        for (Map.Entry<Product, Integer> entry : productMap.entrySet()) {
            Product product = entry.getKey();
            int quantity = entry.getValue();
            order.addItem(new OrderItem(order, product, product.getPrice(), quantity));
        }
        return order;
    }
}

public class DirectOrderFactory implements OrderFactory {
    @Override
    public Order createOrder(Customer customer, Map<Product, Integer> productMap) {
        Map.Entry<Product, Integer> entry = productMap.entrySet().iterator().next();
        Product product = entry.getKey();
        int quantity = entry.getValue();

        Order order = new Order(customer, OrderStatus.ORDERED);
        order.addItem(new OrderItem(order, product, product.getPrice(), quantity));

        return order;
    }
}

CreateOrder 메소드는 구매자의 정보와 구매하려는 상품의 정보와 수량이 담긴 map을 파라미터로 받는다는 점에서 공통점을 갖고 있으므로 주문생성의 책임을 OrderFactory로 분리한다.

 

Factory 선택 Provider 구현

public enum OrderType {
    CART, DIRECT
}

public class OrderFactoryProvider {
    public static OrderFactory getOrderFactory(OrderType type) {
        return switch (type) {
            case CART -> new CartOrderFactory();
            case DIRECT -> new DirectOrderFactory();
        };
    }
}

 

서비스에서 Factory 사용

@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;

    @Transactional
    public Order createOrder(OrderType orderType, Customer customer, Map<Product, Integer> productQuantityMap) {
        OrderFactory factory = OrderFactoryProvider.getOrderFactory(orderType);
        Order order = factory.createOrder(customer, productQuantityMap);
        return orderRepository.save(order);
    }
}

 

패턴을 적용하지 않은 경우

public Order createOrder(OrderType type, Customer customer, Map<Product, Integer> productMap) {
    Order order = new Order(customer, OrderStatus.ORDERED);

    if (type == OrderType.CART) {
        for (Map.Entry<Product, Integer> entry : productMap.entrySet()) {
            Product product = entry.getKey();
            int quantity = entry.getValue();
            order.addItem(new OrderItem(order, product, product.getPrice() quantity));
        }
    } else if (type == OrderType.DIRECT) {
        Map.Entry<Product, Integer> entry = productMap.entrySet().iterator().next();
        Product product = entry.getKey();
        int quantity = entry.getValue();,
        order.addItem(new OrderItem(order, product, product.getPrice(), quantity));
    }

    return orderRepository.save(order);
}
  • OrderService가 객체 생성 책임까지 떠안음 → 단일 책임 원칙(SRP) 위배
  • 조건문 분기 로직이 복잡해짐
  • 새로운 주문 방식 추가 시 Service 수정 필요 → 개방 폐쇄 원칙(OCP) 위배
  • 테스트 어려움: 주문 방식마다 서비스 테스트 필요

그냥 동작만 하게 만들고 끝나는 구조는 확장성과 유지보수성 측면에서 문제를 야기할 수 있다. 팩토리 메소드 패턴을 도입하면서 주문 방식이 많아져도 코드 변경에 유연하게 대응할 수 있고, 서비스 측면에서 주문 자체를 생성하는 로직에 집중할 수 있다는 장점을 얻을 수 있다.