2단계: 객체지향 리팩터링 (중급 기술)
6. 다음 단계로
2단계는 뼈대를 세웠다. 하지만 코드를 다시 읽으면 다음이 눈에 걸린다.
| # | 남은 한계 | 어디서 보이나 | 3단계의 해법 |
|---|---|---|---|
| 1 | 저장소 코드 중복 | InMemory 저장소 3개가 타입만 다른 같은 코드 | 제네릭 Repository<T, ID> + InMemoryRepository<T, ID> 하나 |
| 2 | 여전히 null | findById 가 null 을 돌려주고 서비스가 null 검사를 반복 |
Optional<T>: orElseThrow 한 줄, 체인은 map/flatMap |
| 3 | 집계가 명령형 | for + if + merge 조합. "회원별 상위 3명", "월별 매출"을 추가하려면 루프를 또 쓴다 |
스트림 — groupingBy, sorted, limit, teeing |
| 4 | 할인 정책이 없다 | VIP 10%, 5만 원 이상 3천 원 같은 규칙을 넣으려면 if가 OrderService에 쌓인다 |
함수형 인터페이스 DiscountPolicy + 람다 조합 |
| 5 | 동시 주문에 취약 | Product.stock -= qty는 두 스레드가 동시에 실행하면 재고가 음수가 될 수 있다 |
ExecutorService + ConcurrentHashMap.compute로 원자적 재고 예약 |
| 6 | 보일러플레이트 | Money의 equals/hashCode/toString/getter 30줄. OrderLine 도 같음 |
record — 컴파일러가 생성 |
| 7 | 결제 결과가 예외 | 흔한 결과(잔액 부족)를 예외로 표현. 결과 종류를 컴파일러가 모름 | sealed interface PaymentResult + 패턴 매칭 switch |
특히 5번은 지금까지 한 번도 고려하지 않은 새로운 차원이다. 콘솔 프로그램은 한 스레드지만, 웹 서버는 요청마다 스레드다. "재고 60개 한정판에 100명이 동시에 주문 버튼을 누르면?" — 2단계 코드로는 60개보다 많이 팔릴 수 있다. 3단계의 마지막 시나리오가 정확히 이 상황이다.
다음 레슨: 3단계 — 모던 자바로 다듬기. 제네릭·스트림·Optional·람다·record·sealed로 코드를 줄이고, 스레드 풀로 동시 주문을 안전하게 처리한다.