객체지향 설계 실전
객체지향 설계 실전
도메인 책임 분리, 일급 컬렉션, 예외 계층 설계
책임 분리 원칙
검증(Validation) 위치
- 객체 생성 시점에 검증 → 불변식 보장
- InputView는 입력만, 검증 로직은 도메인 객체 내부에
public class Car {
private final String name;
public Car(String name) {
validate(name); // 생성 시점 검증
this.name = name;
}
private void validate(String name) {
if (name.length() > 5) {
throw new CarRacingException(ErrorCode.INVALID_CAR_NAME);
}
}
}
일급 컬렉션 (First-Class Collection)
컬렉션을 래핑한 도메인 클래스 — 관련 행동을 한 곳에 모음
// Before: 컨트롤러/서비스가 직접 List<Car> 조작
List<Car> cars = new ArrayList<>();
// After: Cars 일급 컬렉션
public class Cars {
private final List<Car> cars;
public Cars(List<Car> cars) {
this.cars = new ArrayList<>(cars);
}
public void race() {
cars.forEachmoveForward;
}
public List<Car> getWinners() {
int maxPosition = getMaxPosition();
return cars.stream()
.filter(car -> car.isAt(maxPosition))
.collect(toList());
}
}
장점
- 컬렉션 관련 로직(우승자 판별, 최대 위치 계산 등)이 한 곳에
- 외부에서 내부 컬렉션 직접 접근 차단
- 테스트 용이
예외 계층 설계
ErrorCode Enum으로 에러 메시지 관리
public enum ErrorCode {
INVALID_CAR_NAME("[ERROR] 자동차 이름은 5자 이하여야 합니다."),
INVALID_ATTEMPT_COUNT("[ERROR] 시도 횟수는 양수여야 합니다."),
DUPLICATE_CAR_NAME("[ERROR] 자동차 이름은 중복될 수 없습니다.");
private final String message;
ErrorCode(String message) {
this.message = message;
}
}
도메인 예외 상속
// 도메인 전용 예외 → IllegalArgumentException 상속
public class CarRacingException extends IllegalArgumentException {
public CarRacingException(ErrorCode errorCode) {
super(errorCode.getMessage());
}
}
장점: 외부에서 IllegalArgumentException으로 캐치 가능하면서 도메인 의미 명확
자동차 경주 최종 설계
CarRacingApplication
└─ CarRacingController
├─ InputView # 입력 전담
├─ OutputView # 출력 전담
├─ RacingService # 경주 진행 (Cars 조작)
└─ WinnerService # 우승자 선별
└─ Cars (일급 컬렉션)
└─ Car # 이름, 위치, 이동 로직
| 클래스 | 책임 |
|---|---|
InputView |
입력만, 검증 없음 |
RacingService |
라운드 진행, Cars 상태 변경 |
WinnerService |
우승자 판별 |
Cars |
컬렉션 조작, 최대값 계산 |
Car |
이름 검증, 이동 결정 |
설계 고민 포인트
Q. 파싱과 검증을 어디서?
InputView → raw String → Parser → 검증된 값 → Service
- Parser: String → 도메인 타입 변환
- Validator: 도메인 규칙 검증 (도메인 객체 내부 or 별도 클래스)
- 순서 주의: 파싱 먼저, 검증은 그 이후
Q. try-catch를 Controller에서만?
// Controller에서 일괄 처리
while (true) {
try {
String input = inputView.readCarNames();
cars = new Cars(carNames); // 여기서 검증 예외 발생 가능
break;
} catch (IllegalArgumentException e) {
outputView.printError(e.getMessage());
}
}