미션 레포지토리
https://github.com/woowacourse/java-subway-map-precourse
GitHub - woowacourse/java-subway-map-precourse
Contribute to woowacourse/java-subway-map-precourse development by creating an account on GitHub.
github.com
미션 구현 PR
https://github.com/woowacourse/java-subway-map-precourse/pull/122
[지하철 노선도] 김민겸 미션 제출합니다. by Mingyum-Kim · Pull Request #122 · woowacourse/java-subway-map-preco
github.com
💪 프로젝트 개요
지하철 역, 노선, 구간을 관리하고 지하철 노선도를 출력하는 프로젝트를 구현한다.
📝 구현 기능 목록
초기 설정을 저장하는 기능
-
교대역, 강남역, 역삼역, 남부터미널역, 양재역, 양재시민의숲역, 매봉역
을 지하철 역으로 등록한다. -
2호선, 3호선, 신분당선
을 지하철 노선으로 등록한다. - 노선마다 역의 정보를 아래와 같이 등록한다.
2호선: 교대역 - 강남역 - 역삼역
3호선: 교대역 - 남부터미널역 - 양재역 - 매봉역
신분당선: 강남역 - 양재역 - 양재시민의숲역
메인 화면을 출력하고 원하는 기능을 입력하는 기능
-
## 메인 화면
과 기능의 목록을 출력한다. -
## 원하는 기능을 선택하세요.
를 출력한다. - 원하는 기능을 숫자 혹은
Q
로 입력받는다.- 주어진 입력에 해당하는 입력임을 검증한다.
- 입력에 따라 적절한 기능을 호출한다.
역을 관리하는 기능
-
## 역 관리 화면
과 관리 목록을 출력한다. -
## 원하는 기능을 선택하세요.
를 출력한다. - 원하는 기능을 숫자 혹은
B
로 입력받는다.- 주어진 입력에 해당하는 입력임을 검증한다.
- 역을 등록한다.
-
## 등록할 역 이름을 입력하세요.
를 출력한다. - 등록할 역 이름을 입력 받는다.
- 빈 문자열이 아님을 검증한다.
- 중복되는 역 이름이 아님을 검증한다.
- 2글자 이상임을 검증한다.
- 역 이름을 저장한다.
-
[INFO] 지하철 역이 등록되었습니다.
를 출력한다.
-
- 역을 조회한다.
-
## 역 목록
를 출력한다. - 저장된 역의 내역을 불러와 출력한다.
-
- 역을 삭제한다.
-
## 삭제할 역 이름을 입력하세요.
를 출력한다. - 역 이름을 입력받는다.
- 빈 문자열이 아님을 검증한다.
- 존재하는 이름이 아닌 경우 다시 입력받는다.
- 입력된 역을 삭제한다.
-
[INFO] 지하철 역이 삭제되었습니다.
를 출력한다.
-
노선을 관리하는 기능
-
## 노선 관리 화면
과 관리 목록을 출력한다. -
## 원하는 기능을 선택하세요.
를 출력한다. - 원하는 기능을 숫자 혹은
B
로 입력받는다.- 주어진 입력에 해당하는 입력임을 검증한다.
- 노선을 등록한다.
-
## 등록할 노선 이름을 입력하세요.
를 출력한다. - 등록할 노선 이름을 입력 받는다.
- 빈 문자열이 아님을 검증한다.
- 중복되는 역 이름이 아님을 검증한다.
- 2글자 이상임을 검증한다.
-
## 등록할 노선의 상행 종점역 이름을 입력하세요.
를 출력한다. - 상행 종점역 이름을 입력한다.
- 저장된 역이 아닌 경우 다시 입력받는다.
-
## 등록할 노선의 하행 종점역 이름을 입력하세요.
를 출력한다. - 하행 종점역 이름을 입력한다.
- 저장된 역이 아닌 경우 다시 입력받는다.
- 노선 이름을 저장한다.
-
[INFO] 지하철 노선이 등록되었습니다.
를 출력한다.
-
- 노선을 조회한다.
-
## 노선 목록
를 출력한다. - 저장된 노선의 이름 내역을 불러와 출력한다.
-
- 노선을 삭제한다.
-
## 삭제할 노선 이름을 입력하세요.
를 출력한다. - 노선 이름을 입력받는다.
- 빈 문자열이 아님을 검증한다.
- 존재하는 이름이 아닌 경우 다시 입력받는다.
- 입력된 노선을 삭제한다.
-
[INFO] 지하철 노선이 삭제되었습니다.
를 출력한다.
-
구간을 관리하는 기능
-
## 구간 관리 화면
과 관리 목록을 출력한다. -
## 원하는 기능을 선택하세요.
를 출력한다. - 원하는 기능을 숫자 혹은
B
로 입력받는다.- 주어진 입력에 해당하는 입력임을 검증한다.
- 구간을 등록한다.
-
## 등록할 노선 이름을 입력하세요.
를 출력한다. - 구간을 추가할 노선 이름을 입력 받는다.
- 존재하는 노선이 아닌 경우, 다시 입력 받는다.
-
## 등록할 역 이름을 입력하세요.
를 출력한다. - 구간에 추가할 역 이름을 입력 받는다.
- 존재하는 역이 아닌 경우, 다시 입력 받는다.
- 역을 추가할 순서를 입력받는다.
- 숫자 입력이 아닌 경우 다시 입력받는다.
- 추가할 수 있는 구간 범위를 넘어간 경우, 다시 입력받는다.
-
[INFO] 구간이 등록되었습니다.
를 출력한다.
-
- 구간을 삭제한다.
-
## 삭제할 구간의 노선을 입력하세요.
를 출력한다. - 노선의 이름을 입력한다.
- 존재하는 노선이 아닌 경우, 다시 입력 받는다.
-
## 삭제할 구간의 역을 입력하세요.
를 출력한다. - 역의 이름을 입력한다.
- 존재하는 역이 아닌 경우, 다시 입력 받는다.
- 해당 노선에 존재하는 역을 삭제한다.
-
[INFO] 구간이 삭제되었습니다.
를 출력한다.
-
지하철 노선도를 출력하는 기능
-
## 지하철 노선도
를 출력한다. - 노선과 해당 노선이 지나는 역의 정보를 차례로 출력한다.
3기 최종 코딩테스트로 출제되었던 지하촐 노선도 미션이다.
이 미션은 내가 그동안 풀어왔던 문제와 사뭇 다른 느낌이었다. 로직이 복잡하거나 출력이 복잡하다기보다는 구현할 기능 자체가 많았고 제한 시간 안에 효과적으로 구현하는 것이 중요한 느낌이었다. 나의 개인적인 생각이지만 내가 그동안 풀어왔던 문제와 스타일이 다르기 때문에, 최종 코딩 테스트를 대비하기에는 조금 아쉬운 느낌이었다. 확실히 최종 코딩 테스트는 그동안 푼 미션과 유사한 유형이 나올 것이기 때문에 비교적 최근 기수의 문제를 풀어보기로 다짐하게 된 경험이었다.
결과적으로 4시간 20분의 시간동안 구현을 완료하였고, 짧은 시간동안 리팩토링하면서 5시간을 채웠다. 테스트 코드는 작성하지 못하였다.
이번 미션에서는 코드가 반복적으로 등장하는 부분이 많았기 때문에, 이러한 코드 중복을 줄이는 것이 관건이었다. 나는 충분히 코드를 줄이지 못하였는데, 그 고민 과정을 써내려가보겠다.
입력에 따라 서로 다른 컨트롤러 객체를 생성하기
이전 다리 건너기 미션에서 시스템 상수를 Enum 클래스에서 정의하여, 입력에 따라 프로그램을 제어하는 과정을 구현하였다. 내가 사용한 방법은 코드 상에서 if 문을 활용한 분기가 필수적일 수밖에 없었고, 이에 따라 코드 중복이 일어났다.
이러한 오류를 줄이기 위해서는 Supplier를 사용할 수 있다. Enum은 그 자체로 객체이기 때문에 여러가지 필드를 가질 수 있다.
따라서 여러 컨트롤러 클래스의 상위 인터페이스를 정의하여, 다형성을 사용해 상황에 맞게 한 줄의 코드로도 서로 다른 컨트롤러 인스턴스를 생성하고 다른 동작을 할 수 있게끔 구현하는 것이다.
우선 결과만 보자면, 왼쪽의 분기문에 의해서 복잡했던 코드가 오른쪽 처럼 간단하게 변경되었다.
public enum FunctionCommand {
STATION("1", StationController::new),
LINE("2", LineController::new),
ROUTE("3", RouteController::new),
PRINT("4", MapController::new),
QUIT("Q", null);
private final String command;
private final Supplier<Controller> controller;
FunctionCommand(String command, Supplier<Controller> controller) {
this.command = command;
this.controller = controller;
}
// 문자열 입력을 통해 Enum 클래스를 반환하는 함수
public static FunctionCommand from(String command) {
return Arrays.stream(FunctionCommand.values())
.filter(element -> element.command.equals(command))
.findFirst()
.orElseThrow(() -> CustomException.from(ErrorMessage.INVALID_FUNCTION_COMMAND));
}
// 컨트롤러 인스턴스를 반환하는 함수
public Controller getInstance() {
return controller.get();
}
}
이렇게 시스템 상수를 저장하는 Enum 클래스를 변형한다.
입력값을 토대로 FunctionCommand 객체를 생성하고, 객체에서 컨트롤러 인스턴스를 꺼내어 공통 로직 실행 함수인 run()을 호출하면 된다.
이러한 방법의 문제점은 컨트롤러 인스턴스를 Enum 객체에서 생성하기 위해 기본 생성자를 호출하는 new 연산자를 사용하는 것이다. 따라서 기본 생성자에서 아래와 같이 의존성을 가지고 있는 클래스에 대한 객체를 생성해주어야 한다.
public StationController() {
inputView = new InputView();
outputView = new OutputView();
}
서비스 레이어의 도입
이 미션의 경우 레포지토리에서 받은 데이터들을 통해 여러 비즈니스 로직을 처리해야한다는 점이 있었다. 즉, 사용자에게 입력을 받고 뷰에 출력하는 기능을 담당하는 컨트롤러 이외에 조금 더 세부적인 예외를 처리하기 위한 또 다른 레이어로 서비스 레이어를 추가하게 되었다.
이렇게 서비스 레이어의 도입으로 인해 단위 테스트 코드를 작성할 수 있게 되었다.
그러나 시간 문제 상 테스트 코드를 작성해보지 못한 것이 아쉬웠다.해당 미션의 경우 중복된 요구사항이 많기 때문에, 코드 중복을 줄일 수 있는 다양한 기법과 유연한 설계를 할 수있는 여지가 많았다.
시간 관계 상 더 깊게 파고드는 것은 미루고, 우선은 다음 미션을 풀어보면서 미션을 빠르게 풀어내는 것에 대한 적응력을 키울 것이다.
'우아한테크코스 > 레벨0' 카테고리의 다른 글
[회고] 우아한 테크코스 6기 백엔드 최종 코딩테스트 회고 & 리팩토링 - 온콜 (1) | 2023.12.25 |
---|---|
[회고] 우아한 테크코스 6기 최종 코딩테스트 후기 feat. 아쉬움 가득 ... (7) | 2023.12.17 |
[회고] 우아한 테크코스 최종 코딩테스트 대비 - 다리 건너기 (0) | 2023.12.13 |
[회고] 우아한테크코스 1차 심사 합격 + 앞으로의 계획 (0) | 2023.12.13 |
[회고] 우아한 테크코스 프리코스 1주차 회고 - 숫자 야구 게임 (5) | 2023.11.27 |