2단계: 객체지향 리팩터링 (중급 기술)
2단계: 객체지향으로 뼈대 세우기
1단계의 배열·null·int를 컬렉션·예외·값 객체로 바꾸고, 결제 수단을 인터페이스로 추상화한다. 이 단계에서 새로 쓰는 기술: 상속과 추상 클래스(템플릿 메서드), 인터페이스와 default 메서드, 사용자 정의 예외 계층,
ArrayList/HashMap/LinkedHashMap, Repository 인터페이스 + 인메모리 구현(생성자 주입),equals/hashCode, 불변Money클래스, enum 상태.
1. 이번 단계의 목표
1.1 새 요구사항
| # | 요구사항 | 세부 |
|---|---|---|
| R7 | 결제 | 주문을 카드·계좌 이체·포인트 중 하나로 결제한다. 카드는 월 한도, 계좌는 잔액, 포인트는 보유 포인트 안에서만 승인 |
| R8 | 결제 상태 | 주문은 CREATED → PAID 또는 CREATED → CANCELLED로만 움직인다. 결제된 주문은 취소 불가 |
| R9 | 포인트 적립 | 카드·계좌 결제 시 1% 적립. 포인트 결제 시에는 적립 없음 |
| R10 | 취소 | 미결제 주문을 취소하면 재고가 복구된다 |
| R11 | 저장 무제한 | 회원·상품·주문 수에 제한이 없다 |
| R12 | 실패 구분 | 재고 부족 / 잔액 부족 / 입력 오류 / 없는 ID를 호출자가 종류별로 구분해 처리할 수 있다 |
1.2 1단계의 한계가 왜 문제였는가
1단계 마지막에 정리한 여섯 가지 아픈 지점을 다시 보자. 이번에는 "무엇이 문제인가"가 아니라 "무엇을 하려는 순간 막히는가"를 적는다.
- 배열 고정: R11(무제한)을 배열로 만족시키려면 과제 2의 확장 코드를 회원·상품·주문 세 곳에 복사해야 한다.
ArrayList는 그 코드가 이미 들어 있는 배열이다. - 선형 탐색: 결제(R7)는 주문 ID로 주문을 찾는 것부터 시작한다. 주문이 늘수록 결제가 느려지는 시스템은 곤란하다.
HashMap은 ID 조회를 O(1)로 만든다. - null 반환: 결제 흐름은 "주문 찾기 → 회원 찾기 → 결제 수단 확인"처럼 조회가 연쇄된다. 매 단계
if (x == null)을 쓰면 정상 경로가 검사 사이에 묻힌다. 예외는 실패를 정상 경로 밖으로 뺀다. - 실패 처리 산만: R12는 "실패 종류를 호출자가 구분"하라고 한다.
println후return으로는 호출자가 왜 실패했는지 알 방법이 없다. 예외 타입이 곧 실패 종류가 된다. - 계산 중복: R9는 "결제액의 1%"인데 1단계는 결제액을 두 곳에서 따로 계산한다. 할인이 들어오면 두 곳이 어긋난다.
Order.total()하나로 모은다. - 금액이 int: 카드 한도에서 결제액을 뺄 때 음수가 되면 안 되고, "잔액 부족" 메시지에는 금액을
12,000원으로 찍어야 한다.Money가 둘 다 책임진다.
그리고 R7의 결제 수단 세 가지. if (type == CARD) {...} else if (type == BANK) {...} else {...}로 짜면 결제 수단이 늘 때마다 pay를 열어야 하고, 각 분기가 잔액 확인·차감·영수증 생성을 반복한다. 인터페이스로 "결제한다"는 계약만 정하고, 수단별 클래스가 계약을 구현하면 OrderService는 수단이 몇 개인지 몰라도 된다.