From 3a295e6e97c85683cfd2a5cfbb614c5b82c1f3f5 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 14:45:11 +0900 Subject: [PATCH 01/15] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 317 ++++++++++++------------------------------------------ 1 file changed, 67 insertions(+), 250 deletions(-) diff --git a/README.md b/README.md index bb0c84ebb..1314f07bb 100644 --- a/README.md +++ b/README.md @@ -1,253 +1,70 @@ # 지하철 노선도 경로 조회 미션 - 등록된 지하철 노선도에서 경로를 조회하는 기능을 구현한다. -
- -## 🚀 기능 요구사항 - -> 프리코스 3주차 미션에서 사용한 코드를 참고해도 무관하다. - -### 초기 설정 -- 프로그램 시작 시 역, 노선, 구간 정보를 초기 설정 해야 한다. -- 거리와 소요 시간은 양의 정수이며 단위는 km와 분을 의미한다. -- 아래의 사전 등록 정보로 반드시 초기 설정을 한다. - -``` - 1. 지하철역으로 교대역, 강남역, 역삼역, 남부터미널역, 양재역, 양재시민의숲역, 매봉역이 등록되어 있다. - 2. 지하철 노선으로 2호선, 3호선, 신분당선이 등록되어 있다. - 3. 노선에 역이 아래와 같이 등록되어 있다.(왼쪽 끝이 상행 종점) - - 2호선: 교대역 - ( 2km / 3분 ) - 강남역 - ( 2km / 3분 ) - 역삼역 - - 3호선: 교대역 - ( 3km / 2분 ) - 남부터미널역 - ( 6km / 5분 ) - 양재역 - ( 1km / 1분 ) - 매봉역 - - 신분당선: 강남역 - ( 2km / 8분 ) - 양재역 - ( 10km / 3분 ) - 양재시민의숲역 - ``` - -### 경로 조회 기능 -- 출발역과 도착역을 입력받아 경로를 조회한다. -- 경로 조회 시 총 거리, 총 소요 시간도 함께 출력한다. -- 경로 조회 기준은 `최단 거리` `최소 시간`이 있다. - -### 예외 처리 -- 경로 조회 시 출발역과 도착역이 같으면 에러를 출력한다. -- 경로 조회 시 출발역과 도착역이 연결되어 있지 않으면 에러를 출력한다. -- 그 외 정상적으로 프로그램이 수행되지 않은 경우 에러를 출력한다. - -
- -## ✍🏻 입출력 요구사항 -- `프로그래밍 실행 결과 예시`와 동일하게 입출력을 구현한다. -- 기대하는 출력 결과는 `[INFO]`를 붙여서 출력한다. 출력값의 형식은 예시와 동일하게 한다. -- 에러 발생 시 `[ERROR]`를 붙여서 출력한다. 에러의 문구는 자유롭게 작성한다. - -### 💻 프로그래밍 실행 결과 예시 -#### 경로 조회 -``` -## 메인 화면 -1. 경로 조회 -Q. 종료 - -## 원하는 기능을 선택하세요. -1 - -## 경로 기준 -1. 최단 거리 -2. 최소 시간 -B. 돌아가기 - -## 원하는 기능을 선택하세요. -1 - -## 출발역을 입력하세요. -교대역 - -## 도착역을 입력하세요. -양재역 - -## 조회 결과 -[INFO] --- -[INFO] 총 거리: 4km -[INFO] 총 소요 시간: 11분 -[INFO] --- -[INFO] 교대역 -[INFO] 강남역 -[INFO] 양재역 - -## 메인 화면 -1. 경로 조회 -Q. 종료 - -... -``` - -#### 에러 출력 예시 - -``` -## 메인 화면 -1. 경로 조회 -Q. 종료 - -## 원하는 기능을 선택하세요. -1 - -## 경로 기준 -1. 최단 거리 -2. 최소 시간 -B. 돌아가기 - -## 원하는 기능을 선택하세요. -1 - -## 출발역을 입력하세요. -강남역 - -## 도착역을 입력하세요. -강남역 - -[ERROR] 출발역과 도착역이 동일합니다. - -## 경로 기준 -1. 최단 거리 -2. 최소 시간 -B. 돌아가기 - -## 원하는 기능을 선택하세요. - -... - -``` - -
- -## 🎱 프로그래밍 요구사항 -- 자바 코드 컨벤션을 지키면서 프로그래밍한다. - - 기본적으로 [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)을 원칙으로 한다. - - 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다. -- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다. -- 3항 연산자를 쓰지 않는다. -- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다. - - 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다. -- else 예약어를 쓰지 않는다. - - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. - - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. -- 프로그래밍 요구사항에서 별도로 변경 불가 안내가 없는 경우 파일 수정과 패키지 이동을 자유롭게 할 수 있다. -- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 `[ERROR]` 로 시작해야 한다. - -### 프로그래밍 요구사항 - Application -- Application 클래스를 활용해 구현해야 한다. -- Application의 패키지 구조는 변경하지 않는다. -- Application 클래스에 있는 Scanner를 사용하고 별도의 Scanner 객체를 만들지 않는다. -```java -public class Application { - public static void main(String[] args) { - final Scanner scanner = new Scanner(System.in); - ... - } -} -``` - -### 프로그래밍 요구사항 - Station, Line -- Station, Line 클래스를 활용하여 지하철역과 노선을 구현해야 한다. -- 제공하는 각 클래스의 기본 생성자를 추가할 수 없다. -- 필드(인스턴스 변수)인 name의 접근 제어자 private을 변경할 수 없다. -- 가능하면 setter 메소드(ex. setXXX)를 추가하지 않고 구현한다. - -```java -public class Station { - private String name; - - public Station(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - // 추가 기능 구현 -} - -``` - -### 프로그래밍 요구사항 - StationRepository, LineRepository -- Station과 Line의 상태를 저장할 수 있는 StationRepository, LineRepository를 제공한다. -- 필요 시 StationRepository, LineRepository 이 외 추가로 Repository를 만들 수 있다. -- 추가로 생성되는 객체에 대해서 XXXRepository 네이밍으로 저장 클래스를 추가한다. -- 객체들의 상태를 관리하기 위해서 XXXRepository 클래스를 활용해 저장 로직을 구현해야 한다. -- 작성된 메서드는 수정할 수 없고, 필요에 따라 메서드를 자유롭게 추가할 수 있다. - -```java -public class StationRepository { - private static final List stations = new ArrayList<>(); - - public static List stations() { - return Collections.unmodifiableList(stations); - } - - public static void addStation(Station station) { - stations.add(station); - } - - public static boolean deleteStation(String name) { - return stations.removeIf(station -> Objects.equals(station.getName(), name)); - } - - public static void deleteAll() { - stations.clear(); - } -} -``` - -
- -## ❗️힌트 -### 최단 경로 라이브러리 -- jgrapht 라이브러리를 활용하면 간편하게 최단거리를 조회할 수 있음 -- Dijkstra 알고리즘을 반드시 이해할 필요는 없고 미션에 적용할 정도로만 이해하면 됨 -- JGraphtTest 클래스의 테스트를 활용하여 미션에 필요한 라이브러리의 기능을 학습할 수 있음 -- 정점(vertex)과 간선(edge), 그리고 가중치 개념을 이용 - - 정점: 지하철역 - - 간선: 지하철역 연결정보 - - 가중치: 거리 or 소요 시간 -- 최단 거리 기준 조회 시 가중치를 거리로 설정 - -```java -@Test -public void getDijkstraShortestPath() { - WeightedMultigraph graph - = new WeightedMultigraph(DefaultWeightedEdge.class); - graph.addVertex("v1"); - graph.addVertex("v2"); - graph.addVertex("v3"); - graph.setEdgeWeight(graph.addEdge("v1", "v2"), 2); - graph.setEdgeWeight(graph.addEdge("v2", "v3"), 2); - graph.setEdgeWeight(graph.addEdge("v1", "v3"), 100); - - DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(graph); - List shortestPath = dijkstraShortestPath.getPath("v3", "v1").getVertexList(); - - assertThat(shortestPath.size()).isEqualTo(3); -} -``` - -#### 테스트 설명 - - - -- 역 사이의 거리를 고려하지 않는 경우 V1->V3 경로가 최단 경로 -- 역 사이의 거리를 고려할 경우 V1->V3 경로의 거리는 100km, V1->V2->V3 경로의 거리는 4km이므로 최단 경로는 V1->V2->V3 - -
- -## 📈 진행 요구사항 -- 미션은 [java-subway-path-precourse 저장소](https://github.com/woowacourse/java-subway-path-precourse) 를 fork/clone해 시작한다. -- 기능을 구현하기 전에 java-subway-path-precourse/docs/README.md 파일에 구현할 기능 목록을 정리해 추가한다. -- git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다. - - [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 남긴다. -- [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 절차를 따라 미션을 제출한다. - - [프리코스 과제 FAQ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse/faq) 문서를 참고하여 진행할 수 있다. -
- -## 📝 License - -This project is [MIT](https://github.com/woowacourse/java-subway-path-precourse/blob/master/LICENSE.md) licensed. +## 기능 구현 목록 +> 기능 작동 순서대로 작성 + +1. 지하철 역 및 노선 초기 설정 +2. 기능 입력 및 검증 + - 예외 사항 + - 1,Q가 아닌 경우 + - 잘못된 형식입니다. +3. 경로 조회 + 1. 기능 입력 및 검증 + - 예외 사항 + - 1,2,B가 아닌 경우 + - 잘못된 형식입니다. + 2. 최단 거리 + 1. 출발역 입력 + - 예외 사항 + - 존재하지 않는 역이면 예외 발생 + - 존재하지 않는 역입니다. + 2. 도착역 입력 + - 존재하지 않는 역이면 예외 발생 + - 존재하지 않는 역입니다. + - 출발역과 같은 경우 + - 출발역과 도착역이 동일합니다. + 3. 계산 + - 예외 사항 + - 출발역과 도착역이 연결되어있지 않은 경우 + - IllegalArgumentException(no such vertex in graph: %s)이 터지면 잡아서 처리 + - 출발역과 도착역이 연결되어있지 않습니다. + 4. 출력 + - 총 거리 + - 총 거리: %dkm + - 총 소요 시간 + - 총 소요 시간: %d분 + - 경로 + 3. 최소 시간 + 1. 출발역 입력 + - 예외 사항 + - 존재하지 않는 역이면 예외 발생 + - 존재하지 않는 역입니다. + 2. 도착역 입력 + - 존재하지 않는 역이면 예외 발생 + - 존재하지 않는 역입니다. + - 출발역과 같은 경우 + - 출발역과 도착역이 동일합니다. + 3. 계산 + - 예외 사항 + - 출발역과 도착역이 연결되어있지 않은 경우 + - IllegalArgumentException(no such vertex in graph: %s)이 터지면 잡아서 처리 + - 출발역과 도착역이 연결되어있지 않습니다. + 4. 출력 + - 총 거리 + - 총 거리: %dkm + - 총 소요 시간 + - 총 소요 시간: %d분 + - 경로 +4. 돌아가기 + 1. 메인 화면으로 이동 +5. 종료 + +## 특이 사항 +* Application 클래스에 있는 Scanner를 사용하고 별도의 Scanner 객체를 만들지 않는다. +- **Station, Line** + - 제공하는 각 클래스의 기본 생성자를 추가할 수 없다. + * 필드(인스턴스 변수)인 name의 접근 제어자 private을 변경할 수 없다. + * 가능하면 setter 메소드(ex. setXXX)를 추가하지 않고 구현한다. +- **StationRepository, LineRepository** + - 작성된 메서드는 수정할 수 없고, 필요에 따라 메서드를 자유롭게 추가할 수 있다. From 37c3e5f4251fc549893493ee412d46ff27345ff5 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 14:45:50 +0900 Subject: [PATCH 02/15] =?UTF-8?q?feat:=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EC=97=AD=20=EB=B0=8F=20=EB=85=B8=EC=84=A0=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/subway/Application.java | 18 +++++- src/main/java/subway/command/Command.java | 5 ++ .../featureA/QueryMenuCommandRegistry.java | 28 +++++++++ .../command/featureA/QueryMenuOption.java | 24 ++++++++ .../featureA/impl/DistanceCommand.java | 21 +++++++ .../command/featureA/impl/TimeCommand.java | 21 +++++++ .../command/main/MainMenuCommandRegistry.java | 26 ++++++++ .../subway/command/main/MainMenuOption.java | 23 +++++++ .../command/main/impl/QueryCommand.java | 37 ++++++++++++ .../java/subway/constant/ErrorMessage.java | 22 +++++++ .../subway/controller/SubwayController.java | 60 +++++++++++++++++++ src/main/java/subway/domain/Line.java | 17 +++++- .../java/subway/domain/RouteRepository.java | 20 +++++++ src/main/java/subway/domain/Station.java | 17 +++++- .../java/subway/domain/StationRepository.java | 9 ++- .../java/subway/service/SubwayService.java | 26 ++++++++ src/main/java/subway/util/Retry.java | 30 ++++++++++ src/main/java/subway/view/OutputView.java | 26 ++++++++ 18 files changed, 424 insertions(+), 6 deletions(-) create mode 100644 src/main/java/subway/command/Command.java create mode 100644 src/main/java/subway/command/featureA/QueryMenuCommandRegistry.java create mode 100644 src/main/java/subway/command/featureA/QueryMenuOption.java create mode 100644 src/main/java/subway/command/featureA/impl/DistanceCommand.java create mode 100644 src/main/java/subway/command/featureA/impl/TimeCommand.java create mode 100644 src/main/java/subway/command/main/MainMenuCommandRegistry.java create mode 100644 src/main/java/subway/command/main/MainMenuOption.java create mode 100644 src/main/java/subway/command/main/impl/QueryCommand.java create mode 100644 src/main/java/subway/constant/ErrorMessage.java create mode 100644 src/main/java/subway/controller/SubwayController.java create mode 100644 src/main/java/subway/domain/RouteRepository.java create mode 100644 src/main/java/subway/service/SubwayService.java create mode 100644 src/main/java/subway/util/Retry.java create mode 100644 src/main/java/subway/view/OutputView.java diff --git a/src/main/java/subway/Application.java b/src/main/java/subway/Application.java index 0bcf786cc..ff95cb796 100644 --- a/src/main/java/subway/Application.java +++ b/src/main/java/subway/Application.java @@ -1,10 +1,26 @@ package subway; +import java.io.IOException; import java.util.Scanner; +import subway.command.featureA.QueryMenuCommandRegistry; +import subway.command.main.MainMenuCommandRegistry; +import subway.controller.SubwayController; +import subway.service.SubwayService; public class Application { public static void main(String[] args) { final Scanner scanner = new Scanner(System.in); - // TODO: 프로그램 구현 + + SubwayService service = new SubwayService(); + QueryMenuCommandRegistry queryMenuCommandRegistry = QueryMenuCommandRegistry.from(service, scanner); + + MainMenuCommandRegistry mainRegistry = MainMenuCommandRegistry.from(queryMenuCommandRegistry, scanner); + + SubwayController subwayController = new SubwayController(mainRegistry, service, scanner); + try { + subwayController.run(); + } catch (IOException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/subway/command/Command.java b/src/main/java/subway/command/Command.java new file mode 100644 index 000000000..a25f0a800 --- /dev/null +++ b/src/main/java/subway/command/Command.java @@ -0,0 +1,5 @@ +package subway.command; + +public interface Command { + void execute(); +} diff --git a/src/main/java/subway/command/featureA/QueryMenuCommandRegistry.java b/src/main/java/subway/command/featureA/QueryMenuCommandRegistry.java new file mode 100644 index 000000000..9b211dd59 --- /dev/null +++ b/src/main/java/subway/command/featureA/QueryMenuCommandRegistry.java @@ -0,0 +1,28 @@ +package subway.command.featureA; + +import java.util.EnumMap; +import java.util.Scanner; +import subway.command.Command; +import subway.command.featureA.impl.DistanceCommand; +import subway.command.featureA.impl.TimeCommand; +import subway.service.SubwayService; + +public class QueryMenuCommandRegistry { + + private final EnumMap commands; + + private QueryMenuCommandRegistry(EnumMap commands) { + this.commands = commands; + } + + public static QueryMenuCommandRegistry from(SubwayService service, Scanner scanner) { + EnumMap map = new EnumMap<>(QueryMenuOption.class); + map.put(QueryMenuOption.A, new DistanceCommand(service, scanner)); + map.put(QueryMenuOption.B, new TimeCommand(service, scanner)); + return new QueryMenuCommandRegistry(map); + } + + public void execute(QueryMenuOption option) { + commands.get(option).execute(); + } +} diff --git a/src/main/java/subway/command/featureA/QueryMenuOption.java b/src/main/java/subway/command/featureA/QueryMenuOption.java new file mode 100644 index 000000000..8bdb2afc6 --- /dev/null +++ b/src/main/java/subway/command/featureA/QueryMenuOption.java @@ -0,0 +1,24 @@ +package subway.command.featureA; + +import java.util.Arrays; +import subway.constant.ErrorMessage; + +public enum QueryMenuOption { + A("1"), + B("2"), + BACK("B"); + + private final String command; + + QueryMenuOption(String command) { + this.command = command; + } + + public static QueryMenuOption from(String command) { + String normalized = command.trim(); + return Arrays.stream(values()) + .filter(opt -> opt.command.equals(normalized)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); + } +} diff --git a/src/main/java/subway/command/featureA/impl/DistanceCommand.java b/src/main/java/subway/command/featureA/impl/DistanceCommand.java new file mode 100644 index 000000000..11a180001 --- /dev/null +++ b/src/main/java/subway/command/featureA/impl/DistanceCommand.java @@ -0,0 +1,21 @@ +package subway.command.featureA.impl; + +import java.util.Scanner; +import subway.command.Command; +import subway.service.SubwayService; + +public class DistanceCommand implements Command { + + private final SubwayService service; + private final Scanner scanner; + + public DistanceCommand(SubwayService service, Scanner scanner) { + this.service = service; + this.scanner = scanner; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/subway/command/featureA/impl/TimeCommand.java b/src/main/java/subway/command/featureA/impl/TimeCommand.java new file mode 100644 index 000000000..9f6f0c5d5 --- /dev/null +++ b/src/main/java/subway/command/featureA/impl/TimeCommand.java @@ -0,0 +1,21 @@ +package subway.command.featureA.impl; + +import java.util.Scanner; +import subway.command.Command; +import subway.service.SubwayService; + +public class TimeCommand implements Command { + + private final SubwayService service; + private final Scanner scanner; + + public TimeCommand(SubwayService service, Scanner scanner) { + this.service = service; + this.scanner = scanner; + } + + @Override + public void execute() { + + } +} diff --git a/src/main/java/subway/command/main/MainMenuCommandRegistry.java b/src/main/java/subway/command/main/MainMenuCommandRegistry.java new file mode 100644 index 000000000..fc1d39c31 --- /dev/null +++ b/src/main/java/subway/command/main/MainMenuCommandRegistry.java @@ -0,0 +1,26 @@ +package subway.command.main; + +import java.util.EnumMap; +import java.util.Scanner; +import subway.command.Command; +import subway.command.featureA.QueryMenuCommandRegistry; +import subway.command.main.impl.QueryCommand; + +public class MainMenuCommandRegistry { + + private final EnumMap commands; + + private MainMenuCommandRegistry(EnumMap commands) { + this.commands = commands; + } + + public static MainMenuCommandRegistry from(QueryMenuCommandRegistry featureARegistry, Scanner scanner) { + EnumMap map = new EnumMap<>(MainMenuOption.class); + map.put(MainMenuOption.A, new QueryCommand(featureARegistry, scanner)); // 기능 선택 커맨드 -> 레지스트리 전달 + return new MainMenuCommandRegistry(map); + } + + public void execute(MainMenuOption option) { + commands.get(option).execute(); + } +} diff --git a/src/main/java/subway/command/main/MainMenuOption.java b/src/main/java/subway/command/main/MainMenuOption.java new file mode 100644 index 000000000..5f040450d --- /dev/null +++ b/src/main/java/subway/command/main/MainMenuOption.java @@ -0,0 +1,23 @@ +package subway.command.main; + +import java.util.Arrays; +import subway.constant.ErrorMessage; + +public enum MainMenuOption { + A("1"), + QUIT("Q"); + + private final String command; + + MainMenuOption(String command) { + this.command = command; + } + + public static MainMenuOption from(String command) { + String normalized = command.trim(); + return Arrays.stream(values()) + .filter(opt -> opt.command.equals(normalized)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.FORMAT_ERROR.getErrorMessage())); + } +} diff --git a/src/main/java/subway/command/main/impl/QueryCommand.java b/src/main/java/subway/command/main/impl/QueryCommand.java new file mode 100644 index 000000000..7781a83e5 --- /dev/null +++ b/src/main/java/subway/command/main/impl/QueryCommand.java @@ -0,0 +1,37 @@ +package subway.command.main.impl; + +import java.util.Scanner; +import subway.command.Command; +import subway.command.featureA.QueryMenuCommandRegistry; +import subway.command.featureA.QueryMenuOption; +import subway.util.Retry; +import subway.view.InputView; + +public class QueryCommand implements Command { + + private final QueryMenuCommandRegistry stationRegistry; + private final Scanner scanner; + + public QueryCommand(QueryMenuCommandRegistry stationRegistry, Scanner scanner) { + this.stationRegistry = stationRegistry; + this.scanner = scanner; + } + + @Override + public void execute() { + QueryMenuOption option = getOption(); + + if (option.equals(QueryMenuOption.BACK)) { + return; + } + + stationRegistry.execute(option); + } + + private QueryMenuOption getOption() { + return Retry.retryUntilSuccess(() -> { + String selection = InputView.readQueryMenuSelection(scanner); + return QueryMenuOption.from(selection); + }); + } +} diff --git a/src/main/java/subway/constant/ErrorMessage.java b/src/main/java/subway/constant/ErrorMessage.java new file mode 100644 index 000000000..b1eb80f1a --- /dev/null +++ b/src/main/java/subway/constant/ErrorMessage.java @@ -0,0 +1,22 @@ +package subway.constant; + +public enum ErrorMessage { + + FORMAT_ERROR("잘못된 형식입니다."), + + NO_EXIST_STATION("존재하지 않는 역입니다."), + SAME_START_END_STATION("출발역과 도착역이 동일합니다."), + NO_CONNECTED_STATIONS("출발역과 도착역이 연결되어있지 않습니다."), + ; + + private static final String ERROR_MESSAGE_PREFIX = "[ERROR] "; + private final String errorMessage; + + ErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public String getErrorMessage(Object... args) { + return ERROR_MESSAGE_PREFIX + String.format(errorMessage, args); + } +} diff --git a/src/main/java/subway/controller/SubwayController.java b/src/main/java/subway/controller/SubwayController.java new file mode 100644 index 000000000..b77a8a9b9 --- /dev/null +++ b/src/main/java/subway/controller/SubwayController.java @@ -0,0 +1,60 @@ +package subway.controller; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import subway.command.main.MainMenuCommandRegistry; +import subway.command.main.MainMenuOption; +import subway.service.SubwayService; +import subway.util.Retry; +import subway.view.InputView; + +public class SubwayController { + + private final MainMenuCommandRegistry registry; + private final SubwayService service; + private final Scanner scanner; + + public SubwayController(MainMenuCommandRegistry registry, SubwayService service, Scanner scanner) { + this.registry = registry; + this.service = service; + this.scanner = scanner; + } + + public void run() throws IOException { + registerFileInfo(); + + while (true) { + MainMenuOption option = readOption(); + + if (option.equals(MainMenuOption.QUIT)) { + return; + } + + registry.execute(option); + } + } + + private void registerFileInfo() throws IOException { + List stations = Arrays.asList("교대역", "강남역", "역삼역", "남부터미널역", "양재역", "양재시민의숲역", "매봉역"); + List> stationPairs = Arrays.asList( + Arrays.asList("교대역", "강남역"), Arrays.asList("강남역", "역삼역"), + Arrays.asList("교대역", "남부터미널역"), Arrays.asList("남부터미널역", "양재역"), Arrays.asList("양재역", "매봉역"), + Arrays.asList("강남역", "양재역"), Arrays.asList("양재역", "양재시민의숲역") + ); + List> weightPairs = Arrays.asList( + Arrays.asList(2,3), Arrays.asList(2,3), + Arrays.asList(3,2), Arrays.asList(6,5), Arrays.asList(1,1), + Arrays.asList(2,8), Arrays.asList(10,3) + ); + service.setStations(stations, stationPairs, weightPairs); + } + + private MainMenuOption readOption() { + return Retry.retryUntilSuccess(() -> { + String selection = InputView.readMainMenuSelection(scanner); + return MainMenuOption.from(selection); + }); + } +} diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java index f4d738d5a..f7117f947 100644 --- a/src/main/java/subway/domain/Line.java +++ b/src/main/java/subway/domain/Line.java @@ -1,12 +1,27 @@ package subway.domain; +import java.util.Objects; + public class Line { - private String name; + private final String name; public Line(String name) { this.name = name; } + public static Line from(String name) { + return new Line(name); + } + + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) { + return false; + } + Line line = (Line) object; + return Objects.equals(name, line.name); + } + public String getName() { return name; } diff --git a/src/main/java/subway/domain/RouteRepository.java b/src/main/java/subway/domain/RouteRepository.java new file mode 100644 index 000000000..ccc3b84a5 --- /dev/null +++ b/src/main/java/subway/domain/RouteRepository.java @@ -0,0 +1,20 @@ +package subway.domain; + +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.WeightedMultigraph; + +public class RouteRepository { + + private static final WeightedMultigraph distanceGraph = new WeightedMultigraph(DefaultWeightedEdge.class); + private static final WeightedMultigraph timeGraph = new WeightedMultigraph(DefaultWeightedEdge.class); + + public static void addStation(Station station) { + distanceGraph.addVertex(station); + timeGraph.addVertex(station); + } + + public static void addRoute(Station start, Station end, int distance, int time) { + distanceGraph.setEdgeWeight(distanceGraph.addEdge(start, end), distance); + timeGraph.setEdgeWeight(timeGraph.addEdge(start, end), time); + } +} diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index bdb142590..79e847781 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -1,12 +1,27 @@ package subway.domain; +import java.util.Objects; + public class Station { - private String name; + + private final String name; public Station(String name) { this.name = name; } + public static Station from(String name) { + return new Station(name); + } + @Override + public boolean equals(Object object) { + if (object == null || getClass() != object.getClass()) { + return false; + } + Station station = (Station) object; + return Objects.equals(name, station.name); + } + public String getName() { return name; } diff --git a/src/main/java/subway/domain/StationRepository.java b/src/main/java/subway/domain/StationRepository.java index 8ed9d103f..9d9724b34 100644 --- a/src/main/java/subway/domain/StationRepository.java +++ b/src/main/java/subway/domain/StationRepository.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; +import subway.constant.ErrorMessage; public class StationRepository { private static final List stations = new ArrayList<>(); @@ -16,8 +16,11 @@ public static void addStation(Station station) { stations.add(station); } - public static boolean deleteStation(String name) { - return stations.removeIf(station -> Objects.equals(station.getName(), name)); + public static Station getStation(String stationName) { + return stations.stream() + .filter(station -> station.getName().equals(stationName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(ErrorMessage.NO_EXIST_STATION.getErrorMessage())); } public static void deleteAll() { diff --git a/src/main/java/subway/service/SubwayService.java b/src/main/java/subway/service/SubwayService.java new file mode 100644 index 000000000..3d9155ee7 --- /dev/null +++ b/src/main/java/subway/service/SubwayService.java @@ -0,0 +1,26 @@ +package subway.service; + +import java.util.List; +import subway.domain.RouteRepository; +import subway.domain.Station; +import subway.domain.StationRepository; + +public class SubwayService { + + public void setStations(List stations, List> stationPairs, List> weightPairs) { + for (String stationName : stations) { + Station station = Station.from(stationName); + StationRepository.addStation(station); + RouteRepository.addStation(station); + } + + for (int i = 0; i < stationPairs.size(); i++) { + Station start = StationRepository.getStation(stationPairs.get(i).get(0)); + Station end = StationRepository.getStation(stationPairs.get(i).get(1)); + int distance = weightPairs.get(i).get(0); + int time = weightPairs.get(i).get(1); + + RouteRepository.addRoute(start, end, distance, time); + } + } +} diff --git a/src/main/java/subway/util/Retry.java b/src/main/java/subway/util/Retry.java new file mode 100644 index 000000000..a20c019af --- /dev/null +++ b/src/main/java/subway/util/Retry.java @@ -0,0 +1,30 @@ +package subway.util; + +import java.util.function.Supplier; +import subway.view.OutputView; + +public final class Retry { + + private Retry() {} + + public static T retryUntilSuccess(Supplier action) { + while (true) { + try { + return action.get(); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } + + public static void retryUntilSuccess(Runnable action) { + while (true) { + try { + action.run(); + return; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } +} diff --git a/src/main/java/subway/view/OutputView.java b/src/main/java/subway/view/OutputView.java new file mode 100644 index 000000000..a51788c34 --- /dev/null +++ b/src/main/java/subway/view/OutputView.java @@ -0,0 +1,26 @@ +package subway.view; + +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public class OutputView { + + private static final String NEW_LINE = System.lineSeparator(); + private static final Locale KOREA = Locale.KOREA; + private static final DateTimeFormatter DATETIME_FMT = + DateTimeFormatter.ofPattern("M월 dd일 E요일 HH:mm", KOREA); + private static final DateTimeFormatter DATE_FMT = + DateTimeFormatter.ofPattern("M월 dd일 E요일", KOREA); + private static final DateTimeFormatter TIME_FMT = + DateTimeFormatter.ofPattern("HH:mm", KOREA); + + private OutputView() { + } + + public static void printErrorMessage(IllegalArgumentException e) { + System.out.println(e.getMessage()); + } + + public static void print() { + } +} From dc451c4ec863dfc5ab41bb595c280d5d8d02b3e1 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 14:51:28 +0900 Subject: [PATCH 03/15] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EA=B8=B0=EB=8A=A5=20=EC=9E=85=EB=A0=A5=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/controller/SubwayController.java | 14 +++++----- src/main/java/subway/util/InputParser.java | 26 +++++++++++++++++++ src/main/java/subway/view/InputView.java | 25 ++++++++++++++++++ 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/main/java/subway/util/InputParser.java create mode 100644 src/main/java/subway/view/InputView.java diff --git a/src/main/java/subway/controller/SubwayController.java b/src/main/java/subway/controller/SubwayController.java index b77a8a9b9..e6afdb033 100644 --- a/src/main/java/subway/controller/SubwayController.java +++ b/src/main/java/subway/controller/SubwayController.java @@ -7,6 +7,7 @@ import subway.command.main.MainMenuCommandRegistry; import subway.command.main.MainMenuOption; import subway.service.SubwayService; +import subway.util.InputParser; import subway.util.Retry; import subway.view.InputView; @@ -44,17 +45,16 @@ private void registerFileInfo() throws IOException { Arrays.asList("강남역", "양재역"), Arrays.asList("양재역", "양재시민의숲역") ); List> weightPairs = Arrays.asList( - Arrays.asList(2,3), Arrays.asList(2,3), - Arrays.asList(3,2), Arrays.asList(6,5), Arrays.asList(1,1), - Arrays.asList(2,8), Arrays.asList(10,3) + Arrays.asList(2, 3), Arrays.asList(2, 3), + Arrays.asList(3, 2), Arrays.asList(6, 5), Arrays.asList(1, 1), + Arrays.asList(2, 8), Arrays.asList(10, 3) ); service.setStations(stations, stationPairs, weightPairs); } private MainMenuOption readOption() { - return Retry.retryUntilSuccess(() -> { - String selection = InputView.readMainMenuSelection(scanner); - return MainMenuOption.from(selection); - }); + return Retry.retryUntilSuccess(() -> + InputParser.parseMainMenuOption(InputView.readMainMenuSelection(scanner)) + ); } } diff --git a/src/main/java/subway/util/InputParser.java b/src/main/java/subway/util/InputParser.java new file mode 100644 index 000000000..54fb590ec --- /dev/null +++ b/src/main/java/subway/util/InputParser.java @@ -0,0 +1,26 @@ +package subway.util; + +import subway.command.featureA.QueryMenuOption; +import subway.command.main.MainMenuOption; + +public final class InputParser { + + private static final String DELIMITER = ","; + private static final String FIRST_DELIMITER = ","; + private static final String SECOND_DELIMITER = "-"; + + private InputParser() { + } + + public static String parseStation(String rawInput) { + return rawInput.strip(); + } + + public static MainMenuOption parseMainMenuOption(String rawInput) { + return MainMenuOption.from(rawInput.strip()); + } + + public static QueryMenuOption parseQueryMenuOption(String rawInput) { + return QueryMenuOption.from(rawInput.strip()); + } +} diff --git a/src/main/java/subway/view/InputView.java b/src/main/java/subway/view/InputView.java new file mode 100644 index 000000000..d6b361d7f --- /dev/null +++ b/src/main/java/subway/view/InputView.java @@ -0,0 +1,25 @@ +package subway.view; + +import java.util.Scanner; + +public class InputView { + + public static String readMainMenuSelection(Scanner scanner) { + System.out.println("\n## 메인 화면\n" + + "1. 경로 조회\n" + + "Q. 종료\n" + + "\n" + + "## 원하는 기능을 선택하세요."); + return scanner.nextLine(); + } + + public static String readQueryMenuSelection(Scanner scanner) { + System.out.println(""); + return scanner.nextLine(); + } + + public static String readStation(Scanner scanner) { + System.out.println(""); + return scanner.nextLine(); + } +} From db885052bfcf3acdaed427fed56a383128dd6a08 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 14:54:00 +0900 Subject: [PATCH 04/15] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20-=20=EA=B8=B0=EB=8A=A5=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/subway/command/main/impl/QueryCommand.java | 8 ++++---- src/main/java/subway/view/InputView.java | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/subway/command/main/impl/QueryCommand.java b/src/main/java/subway/command/main/impl/QueryCommand.java index 7781a83e5..b1b8155d0 100644 --- a/src/main/java/subway/command/main/impl/QueryCommand.java +++ b/src/main/java/subway/command/main/impl/QueryCommand.java @@ -4,6 +4,7 @@ import subway.command.Command; import subway.command.featureA.QueryMenuCommandRegistry; import subway.command.featureA.QueryMenuOption; +import subway.util.InputParser; import subway.util.Retry; import subway.view.InputView; @@ -29,9 +30,8 @@ public void execute() { } private QueryMenuOption getOption() { - return Retry.retryUntilSuccess(() -> { - String selection = InputView.readQueryMenuSelection(scanner); - return QueryMenuOption.from(selection); - }); + return Retry.retryUntilSuccess(() -> + InputParser.parseQueryMenuOption(InputView.readQueryMenuSelection(scanner)) + ); } } diff --git a/src/main/java/subway/view/InputView.java b/src/main/java/subway/view/InputView.java index d6b361d7f..9c7b1117b 100644 --- a/src/main/java/subway/view/InputView.java +++ b/src/main/java/subway/view/InputView.java @@ -14,7 +14,12 @@ public static String readMainMenuSelection(Scanner scanner) { } public static String readQueryMenuSelection(Scanner scanner) { - System.out.println(""); + System.out.println("\n## 경로 기준\n" + + "1. 최단 거리\n" + + "2. 최소 시간\n" + + "B. 돌아가기\n" + + "\n" + + "## 원하는 기능을 선택하세요."); return scanner.nextLine(); } From 815b91c628f203d8311d420e8f6f2d5c81ae5db1 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 15:02:19 +0900 Subject: [PATCH 05/15] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20-=20=EC=B5=9C=EB=8B=A8=20=EA=B1=B0=EB=A6=AC=20-=20?= =?UTF-8?q?=EC=B6=9C=EB=B0=9C=EC=97=AD=EA=B3=BC=20=EB=8F=84=EC=B0=A9?= =?UTF-8?q?=EC=97=AD=20=EC=9E=85=EB=A0=A5=20=EB=B0=8F=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/featureA/impl/DistanceCommand.java | 16 ++++++++++++++++ src/main/java/subway/service/SubwayService.java | 4 ++++ src/main/java/subway/view/InputView.java | 9 +++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/subway/command/featureA/impl/DistanceCommand.java b/src/main/java/subway/command/featureA/impl/DistanceCommand.java index 11a180001..b850593c9 100644 --- a/src/main/java/subway/command/featureA/impl/DistanceCommand.java +++ b/src/main/java/subway/command/featureA/impl/DistanceCommand.java @@ -2,7 +2,12 @@ import java.util.Scanner; import subway.command.Command; +import subway.constant.ErrorMessage; +import subway.domain.Station; import subway.service.SubwayService; +import subway.util.InputParser; +import subway.util.Retry; +import subway.view.InputView; public class DistanceCommand implements Command { @@ -16,6 +21,17 @@ public DistanceCommand(SubwayService service, Scanner scanner) { @Override public void execute() { + Station startStation = Retry.retryUntilSuccess(() -> { + String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); + return service.getStation(stationName); + }); + Station endStation = Retry.retryUntilSuccess(() -> { + String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); + if (startStation.equals(service.getStation(stationName))) { + throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); + } + return service.getStation(stationName); + }); } } diff --git a/src/main/java/subway/service/SubwayService.java b/src/main/java/subway/service/SubwayService.java index 3d9155ee7..3e2078f4c 100644 --- a/src/main/java/subway/service/SubwayService.java +++ b/src/main/java/subway/service/SubwayService.java @@ -23,4 +23,8 @@ public void setStations(List stations, List> stationPairs, RouteRepository.addRoute(start, end, distance, time); } } + + public Station getStation(String station) { + return StationRepository.getStation(station); + } } diff --git a/src/main/java/subway/view/InputView.java b/src/main/java/subway/view/InputView.java index 9c7b1117b..b4aa0aff5 100644 --- a/src/main/java/subway/view/InputView.java +++ b/src/main/java/subway/view/InputView.java @@ -23,8 +23,13 @@ public static String readQueryMenuSelection(Scanner scanner) { return scanner.nextLine(); } - public static String readStation(Scanner scanner) { - System.out.println(""); + public static String readStartStation(Scanner scanner) { + System.out.println("\n## 출발역을 입력하세요."); + return scanner.nextLine(); + } + + public static String readEndStation(Scanner scanner) { + System.out.println("\n## 도착역을 입력하세요."); return scanner.nextLine(); } } From 289dc748cff16ffbe7a5c27ef6cbeee93a718c6d Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 15:15:06 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20-=20=EC=B5=9C=EB=8B=A8=20=EA=B1=B0=EB=A6=AC=20-=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EA=B3=84=EC=82=B0=20=EB=B0=8F=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../featureA/impl/DistanceCommand.java | 11 ++++++++++ src/main/java/subway/domain/Result.java | 22 +++++++++++++++++++ .../java/subway/domain/RouteRepository.java | 15 +++++++++++++ .../java/subway/service/SubwayService.java | 5 +++++ 4 files changed, 53 insertions(+) create mode 100644 src/main/java/subway/domain/Result.java diff --git a/src/main/java/subway/command/featureA/impl/DistanceCommand.java b/src/main/java/subway/command/featureA/impl/DistanceCommand.java index b850593c9..c3f065b9e 100644 --- a/src/main/java/subway/command/featureA/impl/DistanceCommand.java +++ b/src/main/java/subway/command/featureA/impl/DistanceCommand.java @@ -3,6 +3,7 @@ import java.util.Scanner; import subway.command.Command; import subway.constant.ErrorMessage; +import subway.domain.Result; import subway.domain.Station; import subway.service.SubwayService; import subway.util.InputParser; @@ -33,5 +34,15 @@ public void execute() { } return service.getStation(stationName); }); + + Result result = getResult(startStation, endStation); + } + + private Result getResult(Station startStation, Station endStation) { + try { + return service.calculateMinDistanceRoute(startStation, endStation); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + } } } diff --git a/src/main/java/subway/domain/Result.java b/src/main/java/subway/domain/Result.java new file mode 100644 index 000000000..4a8a66e75 --- /dev/null +++ b/src/main/java/subway/domain/Result.java @@ -0,0 +1,22 @@ +package subway.domain; + +import java.util.List; + +public class Result { + + private final List stations; + private final int weight; + + public Result(List stations, int weight) { + this.stations = stations; + this.weight = weight; + } + + public List getStations() { + return stations; + } + + public int getWeight() { + return weight; + } +} diff --git a/src/main/java/subway/domain/RouteRepository.java b/src/main/java/subway/domain/RouteRepository.java index ccc3b84a5..625e1007b 100644 --- a/src/main/java/subway/domain/RouteRepository.java +++ b/src/main/java/subway/domain/RouteRepository.java @@ -1,5 +1,8 @@ package subway.domain; +import java.util.List; +import java.util.stream.Collectors; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.WeightedMultigraph; @@ -7,6 +10,8 @@ public class RouteRepository { private static final WeightedMultigraph distanceGraph = new WeightedMultigraph(DefaultWeightedEdge.class); private static final WeightedMultigraph timeGraph = new WeightedMultigraph(DefaultWeightedEdge.class); + private static final DijkstraShortestPath minDistancePath = new DijkstraShortestPath(distanceGraph); + private static final DijkstraShortestPath minTimePath = new DijkstraShortestPath(timeGraph); public static void addStation(Station station) { distanceGraph.addVertex(station); @@ -17,4 +22,14 @@ public static void addRoute(Station start, Station end, int distance, int time) distanceGraph.setEdgeWeight(distanceGraph.addEdge(start, end), distance); timeGraph.setEdgeWeight(timeGraph.addEdge(start, end), time); } + + public static Result getMinDistanceRoute(Station startStation, Station endStation) { + List stations = minDistancePath.getPath(startStation, endStation).getVertexList(); + List stationNames = stations.stream() + .map(station -> station.getName()) + .collect(Collectors.toList()); + int misDistance = (int) minDistancePath.getPath(startStation, endStation).getWeight(); + + return new Result(stationNames, misDistance); + } } diff --git a/src/main/java/subway/service/SubwayService.java b/src/main/java/subway/service/SubwayService.java index 3e2078f4c..df95d33e5 100644 --- a/src/main/java/subway/service/SubwayService.java +++ b/src/main/java/subway/service/SubwayService.java @@ -1,6 +1,7 @@ package subway.service; import java.util.List; +import subway.domain.Result; import subway.domain.RouteRepository; import subway.domain.Station; import subway.domain.StationRepository; @@ -27,4 +28,8 @@ public void setStations(List stations, List> stationPairs, public Station getStation(String station) { return StationRepository.getStation(station); } + + public Result calculateMinDistanceRoute(Station startStation, Station endStation) { + return RouteRepository.getMinDistanceRoute(startStation, endStation); + } } From 7902adaa09f5ad4c3565fb87713049f4685a0f5c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 15:21:41 +0900 Subject: [PATCH 07/15] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20-=20=EC=B5=9C=EB=8B=A8=20=EA=B1=B0=EB=A6=AC=20-=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EA=B3=84=EC=82=B0=20=EB=B0=8F=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../featureA/impl/DistanceCommand.java | 2 ++ src/main/java/subway/domain/Result.java | 16 +++++++++----- .../java/subway/domain/RouteRepository.java | 21 ++++++++++++++++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/main/java/subway/command/featureA/impl/DistanceCommand.java b/src/main/java/subway/command/featureA/impl/DistanceCommand.java index c3f065b9e..e5a8b9a98 100644 --- a/src/main/java/subway/command/featureA/impl/DistanceCommand.java +++ b/src/main/java/subway/command/featureA/impl/DistanceCommand.java @@ -9,6 +9,7 @@ import subway.util.InputParser; import subway.util.Retry; import subway.view.InputView; +import subway.view.OutputView; public class DistanceCommand implements Command { @@ -36,6 +37,7 @@ public void execute() { }); Result result = getResult(startStation, endStation); + } private Result getResult(Station startStation, Station endStation) { diff --git a/src/main/java/subway/domain/Result.java b/src/main/java/subway/domain/Result.java index 4a8a66e75..764fa02b6 100644 --- a/src/main/java/subway/domain/Result.java +++ b/src/main/java/subway/domain/Result.java @@ -5,18 +5,24 @@ public class Result { private final List stations; - private final int weight; + private final int distance; + private final int time; - public Result(List stations, int weight) { + public Result(List stations, int distance, int time) { this.stations = stations; - this.weight = weight; + this.distance = distance; + this.time = time; } public List getStations() { return stations; } - public int getWeight() { - return weight; + public int getDistance() { + return distance; + } + + public int getTime() { + return time; } } diff --git a/src/main/java/subway/domain/RouteRepository.java b/src/main/java/subway/domain/RouteRepository.java index 625e1007b..dcadce335 100644 --- a/src/main/java/subway/domain/RouteRepository.java +++ b/src/main/java/subway/domain/RouteRepository.java @@ -8,8 +8,10 @@ public class RouteRepository { - private static final WeightedMultigraph distanceGraph = new WeightedMultigraph(DefaultWeightedEdge.class); - private static final WeightedMultigraph timeGraph = new WeightedMultigraph(DefaultWeightedEdge.class); + private static final WeightedMultigraph distanceGraph = new WeightedMultigraph( + DefaultWeightedEdge.class); + private static final WeightedMultigraph timeGraph = new WeightedMultigraph( + DefaultWeightedEdge.class); private static final DijkstraShortestPath minDistancePath = new DijkstraShortestPath(distanceGraph); private static final DijkstraShortestPath minTimePath = new DijkstraShortestPath(timeGraph); @@ -25,11 +27,24 @@ public static void addRoute(Station start, Station end, int distance, int time) public static Result getMinDistanceRoute(Station startStation, Station endStation) { List stations = minDistancePath.getPath(startStation, endStation).getVertexList(); + List stationNames = stations.stream() .map(station -> station.getName()) .collect(Collectors.toList()); int misDistance = (int) minDistancePath.getPath(startStation, endStation).getWeight(); + int timeSum = getTime(stations); + + return new Result(stationNames, misDistance, timeSum); + } + + private static int getTime(List stations) { + int timeSum = 0; + for (int i = 0; i < stations.size() - 1; i++) { + Station firstStation = stations.get(i); + Station secondStation = stations.get(i + 1); - return new Result(stationNames, misDistance); + timeSum += (int) minTimePath.getPath(firstStation, secondStation).getWeight(); + } + return timeSum; } } From c1162f7a7f5f433764b54db828c36cbcde5983c7 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 15:24:59 +0900 Subject: [PATCH 08/15] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20-=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/command/featureA/impl/DistanceCommand.java | 1 + src/main/java/subway/view/OutputView.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/subway/command/featureA/impl/DistanceCommand.java b/src/main/java/subway/command/featureA/impl/DistanceCommand.java index e5a8b9a98..2571bd62d 100644 --- a/src/main/java/subway/command/featureA/impl/DistanceCommand.java +++ b/src/main/java/subway/command/featureA/impl/DistanceCommand.java @@ -38,6 +38,7 @@ public void execute() { Result result = getResult(startStation, endStation); + OutputView.printRoute(result); } private Result getResult(Station startStation, Station endStation) { diff --git a/src/main/java/subway/view/OutputView.java b/src/main/java/subway/view/OutputView.java index a51788c34..b2c2c67b8 100644 --- a/src/main/java/subway/view/OutputView.java +++ b/src/main/java/subway/view/OutputView.java @@ -2,6 +2,7 @@ import java.time.format.DateTimeFormatter; import java.util.Locale; +import subway.domain.Result; public class OutputView { @@ -21,6 +22,14 @@ public static void printErrorMessage(IllegalArgumentException e) { System.out.println(e.getMessage()); } - public static void print() { + public static void printRoute(Result result) { + System.out.println("\n## 조회 결과"); + System.out.println("[INFO] ---"); + System.out.printf("[INFO] 총 거리: %dkm\n", result.getDistance()); + System.out.printf("[INFO] 총 소요 시간: %d분\n", result.getTime()); + System.out.println("[INFO] ---"); + for (String station : result.getStations()) { + System.out.println("[INFO] " + station); + } } } From 9af644a758f12ed79365312fafb5cefd7eaad979 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 15:31:38 +0900 Subject: [PATCH 09/15] =?UTF-8?q?feat:=20=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20-=20=EC=B5=9C=EC=86=8C=20=EC=8B=9C=EA=B0=84=20-=20?= =?UTF-8?q?=EB=AA=A8=EB=93=A0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/featureA/impl/TimeCommand.java | 30 +++++++++++++++++++ .../java/subway/domain/RouteRepository.java | 25 +++++++++++++++- .../java/subway/service/SubwayService.java | 4 +++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/main/java/subway/command/featureA/impl/TimeCommand.java b/src/main/java/subway/command/featureA/impl/TimeCommand.java index 9f6f0c5d5..f39974df8 100644 --- a/src/main/java/subway/command/featureA/impl/TimeCommand.java +++ b/src/main/java/subway/command/featureA/impl/TimeCommand.java @@ -2,7 +2,14 @@ import java.util.Scanner; import subway.command.Command; +import subway.constant.ErrorMessage; +import subway.domain.Result; +import subway.domain.Station; import subway.service.SubwayService; +import subway.util.InputParser; +import subway.util.Retry; +import subway.view.InputView; +import subway.view.OutputView; public class TimeCommand implements Command { @@ -16,6 +23,29 @@ public TimeCommand(SubwayService service, Scanner scanner) { @Override public void execute() { + Station startStation = Retry.retryUntilSuccess(() -> { + String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); + return service.getStation(stationName); + }); + Station endStation = Retry.retryUntilSuccess(() -> { + String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); + if (startStation.equals(service.getStation(stationName))) { + throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); + } + return service.getStation(stationName); + }); + + Result result = getResult(startStation, endStation); + + OutputView.printRoute(result); + } + + private Result getResult(Station startStation, Station endStation) { + try { + return service.calculateMinTimeRoute(startStation, endStation); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + } } } diff --git a/src/main/java/subway/domain/RouteRepository.java b/src/main/java/subway/domain/RouteRepository.java index dcadce335..90c1457aa 100644 --- a/src/main/java/subway/domain/RouteRepository.java +++ b/src/main/java/subway/domain/RouteRepository.java @@ -29,7 +29,7 @@ public static Result getMinDistanceRoute(Station startStation, Station endStatio List stations = minDistancePath.getPath(startStation, endStation).getVertexList(); List stationNames = stations.stream() - .map(station -> station.getName()) + .map(Station::getName) .collect(Collectors.toList()); int misDistance = (int) minDistancePath.getPath(startStation, endStation).getWeight(); int timeSum = getTime(stations); @@ -47,4 +47,27 @@ private static int getTime(List stations) { } return timeSum; } + + public static Result getMinTimeRoute(Station startStation, Station endStation) { + List stations = minTimePath.getPath(startStation, endStation).getVertexList(); + + List stationNames = stations.stream() + .map(Station::getName) + .collect(Collectors.toList()); + int distanceSum = getDistance(stations); + int minTime = (int) minTimePath.getPath(startStation, endStation).getWeight(); + + return new Result(stationNames, distanceSum, minTime); + } + + private static int getDistance(List stations) { + int distanceSum = 0; + for (int i = 0; i < stations.size() - 1; i++) { + Station firstStation = stations.get(i); + Station secondStation = stations.get(i + 1); + + distanceSum += (int) minDistancePath.getPath(firstStation, secondStation).getWeight(); + } + return distanceSum; + } } diff --git a/src/main/java/subway/service/SubwayService.java b/src/main/java/subway/service/SubwayService.java index df95d33e5..9da5551e2 100644 --- a/src/main/java/subway/service/SubwayService.java +++ b/src/main/java/subway/service/SubwayService.java @@ -32,4 +32,8 @@ public Station getStation(String station) { public Result calculateMinDistanceRoute(Station startStation, Station endStation) { return RouteRepository.getMinDistanceRoute(startStation, endStation); } + + public Result calculateMinTimeRoute(Station startStation, Station endStation) { + return RouteRepository.getMinTimeRoute(startStation, endStation); + } } From 2ca82d52aa224a4602883a48efb084aeba057b30 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 15:46:35 +0900 Subject: [PATCH 10/15] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=B6=94=EC=B6=9C=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +++---- src/main/java/subway/Application.java | 2 +- .../command/main/MainMenuCommandRegistry.java | 6 +++--- .../command/main/impl/QueryCommand.java | 4 ++-- .../QueryMenuCommandRegistry.java | 6 +++--- .../{featureA => query}/QueryMenuOption.java | 2 +- .../impl/DistanceCommand.java | 21 ++++++++++++------- .../{featureA => query}/impl/TimeCommand.java | 21 ++++++++++++------- src/main/java/subway/domain/Line.java | 13 ------------ src/main/java/subway/domain/Station.java | 2 -- .../java/subway/service/SubwayService.java | 7 +++++++ src/main/java/subway/util/InputParser.java | 6 +----- src/main/java/subway/util/Retry.java | 11 ---------- src/main/java/subway/view/OutputView.java | 11 ---------- 14 files changed, 50 insertions(+), 70 deletions(-) rename src/main/java/subway/command/{featureA => query}/QueryMenuCommandRegistry.java (85%) rename src/main/java/subway/command/{featureA => query}/QueryMenuOption.java (94%) rename src/main/java/subway/command/{featureA => query}/impl/DistanceCommand.java (81%) rename src/main/java/subway/command/{featureA => query}/impl/TimeCommand.java (81%) diff --git a/README.md b/README.md index 1314f07bb..de63c02df 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ - 1,2,B가 아닌 경우 - 잘못된 형식입니다. 2. 최단 거리 - 1. 출발역 입력 + 1. 출발역 입력 및 검증 - 예외 사항 - 존재하지 않는 역이면 예외 발생 - 존재하지 않는 역입니다. - 2. 도착역 입력 + 2. 도착역 입력 및 검증 - 존재하지 않는 역이면 예외 발생 - 존재하지 않는 역입니다. - 출발역과 같은 경우 @@ -36,11 +36,11 @@ - 총 소요 시간: %d분 - 경로 3. 최소 시간 - 1. 출발역 입력 + 1. 출발역 입력 및 검증 - 예외 사항 - 존재하지 않는 역이면 예외 발생 - 존재하지 않는 역입니다. - 2. 도착역 입력 + 2. 도착역 입력 및 검증 - 존재하지 않는 역이면 예외 발생 - 존재하지 않는 역입니다. - 출발역과 같은 경우 diff --git a/src/main/java/subway/Application.java b/src/main/java/subway/Application.java index ff95cb796..692e72112 100644 --- a/src/main/java/subway/Application.java +++ b/src/main/java/subway/Application.java @@ -2,7 +2,7 @@ import java.io.IOException; import java.util.Scanner; -import subway.command.featureA.QueryMenuCommandRegistry; +import subway.command.query.QueryMenuCommandRegistry; import subway.command.main.MainMenuCommandRegistry; import subway.controller.SubwayController; import subway.service.SubwayService; diff --git a/src/main/java/subway/command/main/MainMenuCommandRegistry.java b/src/main/java/subway/command/main/MainMenuCommandRegistry.java index fc1d39c31..7bfbaa952 100644 --- a/src/main/java/subway/command/main/MainMenuCommandRegistry.java +++ b/src/main/java/subway/command/main/MainMenuCommandRegistry.java @@ -3,7 +3,7 @@ import java.util.EnumMap; import java.util.Scanner; import subway.command.Command; -import subway.command.featureA.QueryMenuCommandRegistry; +import subway.command.query.QueryMenuCommandRegistry; import subway.command.main.impl.QueryCommand; public class MainMenuCommandRegistry { @@ -14,9 +14,9 @@ private MainMenuCommandRegistry(EnumMap commands) { this.commands = commands; } - public static MainMenuCommandRegistry from(QueryMenuCommandRegistry featureARegistry, Scanner scanner) { + public static MainMenuCommandRegistry from(QueryMenuCommandRegistry queryRegistry, Scanner scanner) { EnumMap map = new EnumMap<>(MainMenuOption.class); - map.put(MainMenuOption.A, new QueryCommand(featureARegistry, scanner)); // 기능 선택 커맨드 -> 레지스트리 전달 + map.put(MainMenuOption.A, new QueryCommand(queryRegistry, scanner)); return new MainMenuCommandRegistry(map); } diff --git a/src/main/java/subway/command/main/impl/QueryCommand.java b/src/main/java/subway/command/main/impl/QueryCommand.java index b1b8155d0..1b3878b71 100644 --- a/src/main/java/subway/command/main/impl/QueryCommand.java +++ b/src/main/java/subway/command/main/impl/QueryCommand.java @@ -2,8 +2,8 @@ import java.util.Scanner; import subway.command.Command; -import subway.command.featureA.QueryMenuCommandRegistry; -import subway.command.featureA.QueryMenuOption; +import subway.command.query.QueryMenuCommandRegistry; +import subway.command.query.QueryMenuOption; import subway.util.InputParser; import subway.util.Retry; import subway.view.InputView; diff --git a/src/main/java/subway/command/featureA/QueryMenuCommandRegistry.java b/src/main/java/subway/command/query/QueryMenuCommandRegistry.java similarity index 85% rename from src/main/java/subway/command/featureA/QueryMenuCommandRegistry.java rename to src/main/java/subway/command/query/QueryMenuCommandRegistry.java index 9b211dd59..390c12170 100644 --- a/src/main/java/subway/command/featureA/QueryMenuCommandRegistry.java +++ b/src/main/java/subway/command/query/QueryMenuCommandRegistry.java @@ -1,10 +1,10 @@ -package subway.command.featureA; +package subway.command.query; import java.util.EnumMap; import java.util.Scanner; import subway.command.Command; -import subway.command.featureA.impl.DistanceCommand; -import subway.command.featureA.impl.TimeCommand; +import subway.command.query.impl.DistanceCommand; +import subway.command.query.impl.TimeCommand; import subway.service.SubwayService; public class QueryMenuCommandRegistry { diff --git a/src/main/java/subway/command/featureA/QueryMenuOption.java b/src/main/java/subway/command/query/QueryMenuOption.java similarity index 94% rename from src/main/java/subway/command/featureA/QueryMenuOption.java rename to src/main/java/subway/command/query/QueryMenuOption.java index 8bdb2afc6..d930812b0 100644 --- a/src/main/java/subway/command/featureA/QueryMenuOption.java +++ b/src/main/java/subway/command/query/QueryMenuOption.java @@ -1,4 +1,4 @@ -package subway.command.featureA; +package subway.command.query; import java.util.Arrays; import subway.constant.ErrorMessage; diff --git a/src/main/java/subway/command/featureA/impl/DistanceCommand.java b/src/main/java/subway/command/query/impl/DistanceCommand.java similarity index 81% rename from src/main/java/subway/command/featureA/impl/DistanceCommand.java rename to src/main/java/subway/command/query/impl/DistanceCommand.java index 2571bd62d..67f5ea283 100644 --- a/src/main/java/subway/command/featureA/impl/DistanceCommand.java +++ b/src/main/java/subway/command/query/impl/DistanceCommand.java @@ -1,4 +1,4 @@ -package subway.command.featureA.impl; +package subway.command.query.impl; import java.util.Scanner; import subway.command.Command; @@ -23,22 +23,29 @@ public DistanceCommand(SubwayService service, Scanner scanner) { @Override public void execute() { - Station startStation = Retry.retryUntilSuccess(() -> { + Station startStation = getStartStation(); + Station endStation = getEndStation(startStation); + + Result result = getResult(startStation, endStation); + + OutputView.printRoute(result); + } + + private Station getStartStation() { + return Retry.retryUntilSuccess(() -> { String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); return service.getStation(stationName); }); + } - Station endStation = Retry.retryUntilSuccess(() -> { + private Station getEndStation(Station startStation) { + return Retry.retryUntilSuccess(() -> { String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); if (startStation.equals(service.getStation(stationName))) { throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); } return service.getStation(stationName); }); - - Result result = getResult(startStation, endStation); - - OutputView.printRoute(result); } private Result getResult(Station startStation, Station endStation) { diff --git a/src/main/java/subway/command/featureA/impl/TimeCommand.java b/src/main/java/subway/command/query/impl/TimeCommand.java similarity index 81% rename from src/main/java/subway/command/featureA/impl/TimeCommand.java rename to src/main/java/subway/command/query/impl/TimeCommand.java index f39974df8..9854d136f 100644 --- a/src/main/java/subway/command/featureA/impl/TimeCommand.java +++ b/src/main/java/subway/command/query/impl/TimeCommand.java @@ -1,4 +1,4 @@ -package subway.command.featureA.impl; +package subway.command.query.impl; import java.util.Scanner; import subway.command.Command; @@ -23,22 +23,29 @@ public TimeCommand(SubwayService service, Scanner scanner) { @Override public void execute() { - Station startStation = Retry.retryUntilSuccess(() -> { + Station startStation = getStartStation(); + Station endStation = getEndStation(startStation); + + Result result = getResult(startStation, endStation); + + OutputView.printRoute(result); + } + + private Station getStartStation() { + return Retry.retryUntilSuccess(() -> { String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); return service.getStation(stationName); }); + } - Station endStation = Retry.retryUntilSuccess(() -> { + private Station getEndStation(Station startStation) { + return Retry.retryUntilSuccess(() -> { String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); if (startStation.equals(service.getStation(stationName))) { throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); } return service.getStation(stationName); }); - - Result result = getResult(startStation, endStation); - - OutputView.printRoute(result); } private Result getResult(Station startStation, Station endStation) { diff --git a/src/main/java/subway/domain/Line.java b/src/main/java/subway/domain/Line.java index f7117f947..4e47d7f1d 100644 --- a/src/main/java/subway/domain/Line.java +++ b/src/main/java/subway/domain/Line.java @@ -1,7 +1,5 @@ package subway.domain; -import java.util.Objects; - public class Line { private final String name; @@ -13,18 +11,7 @@ public static Line from(String name) { return new Line(name); } - @Override - public boolean equals(Object object) { - if (object == null || getClass() != object.getClass()) { - return false; - } - Line line = (Line) object; - return Objects.equals(name, line.name); - } - public String getName() { return name; } - - // 추가 기능 구현 } diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index 79e847781..2a94b5c01 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -25,6 +25,4 @@ public boolean equals(Object object) { public String getName() { return name; } - - // 추가 기능 구현 } diff --git a/src/main/java/subway/service/SubwayService.java b/src/main/java/subway/service/SubwayService.java index 9da5551e2..17cac4b43 100644 --- a/src/main/java/subway/service/SubwayService.java +++ b/src/main/java/subway/service/SubwayService.java @@ -9,12 +9,19 @@ public class SubwayService { public void setStations(List stations, List> stationPairs, List> weightPairs) { + setStations(stations); + setRoutes(stationPairs, weightPairs); + } + + private static void setStations(List stations) { for (String stationName : stations) { Station station = Station.from(stationName); StationRepository.addStation(station); RouteRepository.addStation(station); } + } + private static void setRoutes(List> stationPairs, List> weightPairs) { for (int i = 0; i < stationPairs.size(); i++) { Station start = StationRepository.getStation(stationPairs.get(i).get(0)); Station end = StationRepository.getStation(stationPairs.get(i).get(1)); diff --git a/src/main/java/subway/util/InputParser.java b/src/main/java/subway/util/InputParser.java index 54fb590ec..1def4054d 100644 --- a/src/main/java/subway/util/InputParser.java +++ b/src/main/java/subway/util/InputParser.java @@ -1,14 +1,10 @@ package subway.util; -import subway.command.featureA.QueryMenuOption; +import subway.command.query.QueryMenuOption; import subway.command.main.MainMenuOption; public final class InputParser { - private static final String DELIMITER = ","; - private static final String FIRST_DELIMITER = ","; - private static final String SECOND_DELIMITER = "-"; - private InputParser() { } diff --git a/src/main/java/subway/util/Retry.java b/src/main/java/subway/util/Retry.java index a20c019af..5f6e82a8a 100644 --- a/src/main/java/subway/util/Retry.java +++ b/src/main/java/subway/util/Retry.java @@ -16,15 +16,4 @@ public static T retryUntilSuccess(Supplier action) { } } } - - public static void retryUntilSuccess(Runnable action) { - while (true) { - try { - action.run(); - return; - } catch (IllegalArgumentException e) { - OutputView.printErrorMessage(e); - } - } - } } diff --git a/src/main/java/subway/view/OutputView.java b/src/main/java/subway/view/OutputView.java index b2c2c67b8..1506f2947 100644 --- a/src/main/java/subway/view/OutputView.java +++ b/src/main/java/subway/view/OutputView.java @@ -1,20 +1,9 @@ package subway.view; -import java.time.format.DateTimeFormatter; -import java.util.Locale; import subway.domain.Result; public class OutputView { - private static final String NEW_LINE = System.lineSeparator(); - private static final Locale KOREA = Locale.KOREA; - private static final DateTimeFormatter DATETIME_FMT = - DateTimeFormatter.ofPattern("M월 dd일 E요일 HH:mm", KOREA); - private static final DateTimeFormatter DATE_FMT = - DateTimeFormatter.ofPattern("M월 dd일 E요일", KOREA); - private static final DateTimeFormatter TIME_FMT = - DateTimeFormatter.ofPattern("HH:mm", KOREA); - private OutputView() { } From 3e1e17388d3e96ab07dccc6370d6695bd5608f10 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 17:11:06 +0900 Subject: [PATCH 11/15] =?UTF-8?q?fix:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/main/MainMenuCommandRegistry.java | 2 +- .../subway/command/main/MainMenuOption.java | 2 +- .../query/QueryMenuCommandRegistry.java | 4 ++-- .../subway/command/query/QueryMenuOption.java | 4 ++-- .../command/query/impl/DistanceCommand.java | 6 +---- .../command/query/impl/TimeCommand.java | 6 +---- src/main/java/subway/domain/Result.java | 24 +------------------ .../java/subway/domain/RouteRepository.java | 14 +++++++++-- src/main/java/subway/domain/Station.java | 5 ++++ src/main/java/subway/view/OutputView.java | 6 ++--- 10 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/main/java/subway/command/main/MainMenuCommandRegistry.java b/src/main/java/subway/command/main/MainMenuCommandRegistry.java index 7bfbaa952..a7a92e612 100644 --- a/src/main/java/subway/command/main/MainMenuCommandRegistry.java +++ b/src/main/java/subway/command/main/MainMenuCommandRegistry.java @@ -16,7 +16,7 @@ private MainMenuCommandRegistry(EnumMap commands) { public static MainMenuCommandRegistry from(QueryMenuCommandRegistry queryRegistry, Scanner scanner) { EnumMap map = new EnumMap<>(MainMenuOption.class); - map.put(MainMenuOption.A, new QueryCommand(queryRegistry, scanner)); + map.put(MainMenuOption.QUERY, new QueryCommand(queryRegistry, scanner)); return new MainMenuCommandRegistry(map); } diff --git a/src/main/java/subway/command/main/MainMenuOption.java b/src/main/java/subway/command/main/MainMenuOption.java index 5f040450d..8b27cfa7b 100644 --- a/src/main/java/subway/command/main/MainMenuOption.java +++ b/src/main/java/subway/command/main/MainMenuOption.java @@ -4,7 +4,7 @@ import subway.constant.ErrorMessage; public enum MainMenuOption { - A("1"), + QUERY("1"), QUIT("Q"); private final String command; diff --git a/src/main/java/subway/command/query/QueryMenuCommandRegistry.java b/src/main/java/subway/command/query/QueryMenuCommandRegistry.java index 390c12170..53da03250 100644 --- a/src/main/java/subway/command/query/QueryMenuCommandRegistry.java +++ b/src/main/java/subway/command/query/QueryMenuCommandRegistry.java @@ -17,8 +17,8 @@ private QueryMenuCommandRegistry(EnumMap commands) { public static QueryMenuCommandRegistry from(SubwayService service, Scanner scanner) { EnumMap map = new EnumMap<>(QueryMenuOption.class); - map.put(QueryMenuOption.A, new DistanceCommand(service, scanner)); - map.put(QueryMenuOption.B, new TimeCommand(service, scanner)); + map.put(QueryMenuOption.DISTANCE, new DistanceCommand(service, scanner)); + map.put(QueryMenuOption.TIME, new TimeCommand(service, scanner)); return new QueryMenuCommandRegistry(map); } diff --git a/src/main/java/subway/command/query/QueryMenuOption.java b/src/main/java/subway/command/query/QueryMenuOption.java index d930812b0..c2bdd598a 100644 --- a/src/main/java/subway/command/query/QueryMenuOption.java +++ b/src/main/java/subway/command/query/QueryMenuOption.java @@ -4,8 +4,8 @@ import subway.constant.ErrorMessage; public enum QueryMenuOption { - A("1"), - B("2"), + DISTANCE("1"), + TIME("2"), BACK("B"); private final String command; diff --git a/src/main/java/subway/command/query/impl/DistanceCommand.java b/src/main/java/subway/command/query/impl/DistanceCommand.java index 67f5ea283..14812f1a4 100644 --- a/src/main/java/subway/command/query/impl/DistanceCommand.java +++ b/src/main/java/subway/command/query/impl/DistanceCommand.java @@ -49,10 +49,6 @@ private Station getEndStation(Station startStation) { } private Result getResult(Station startStation, Station endStation) { - try { - return service.calculateMinDistanceRoute(startStation, endStation); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); - } + return service.calculateMinDistanceRoute(startStation, endStation); } } diff --git a/src/main/java/subway/command/query/impl/TimeCommand.java b/src/main/java/subway/command/query/impl/TimeCommand.java index 9854d136f..b607d700e 100644 --- a/src/main/java/subway/command/query/impl/TimeCommand.java +++ b/src/main/java/subway/command/query/impl/TimeCommand.java @@ -49,10 +49,6 @@ private Station getEndStation(Station startStation) { } private Result getResult(Station startStation, Station endStation) { - try { - return service.calculateMinTimeRoute(startStation, endStation); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); - } + return service.calculateMinTimeRoute(startStation, endStation); } } diff --git a/src/main/java/subway/domain/Result.java b/src/main/java/subway/domain/Result.java index 764fa02b6..2f589f988 100644 --- a/src/main/java/subway/domain/Result.java +++ b/src/main/java/subway/domain/Result.java @@ -2,27 +2,5 @@ import java.util.List; -public class Result { - - private final List stations; - private final int distance; - private final int time; - - public Result(List stations, int distance, int time) { - this.stations = stations; - this.distance = distance; - this.time = time; - } - - public List getStations() { - return stations; - } - - public int getDistance() { - return distance; - } - - public int getTime() { - return time; - } +public record Result(List stations, int distance, int time) { } diff --git a/src/main/java/subway/domain/RouteRepository.java b/src/main/java/subway/domain/RouteRepository.java index 90c1457aa..6d556c119 100644 --- a/src/main/java/subway/domain/RouteRepository.java +++ b/src/main/java/subway/domain/RouteRepository.java @@ -2,9 +2,11 @@ import java.util.List; import java.util.stream.Collectors; +import org.jgrapht.GraphPath; import org.jgrapht.alg.shortestpath.DijkstraShortestPath; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.WeightedMultigraph; +import subway.constant.ErrorMessage; public class RouteRepository { @@ -26,8 +28,12 @@ public static void addRoute(Station start, Station end, int distance, int time) } public static Result getMinDistanceRoute(Station startStation, Station endStation) { - List stations = minDistancePath.getPath(startStation, endStation).getVertexList(); + GraphPath path = minDistancePath.getPath(startStation, endStation); + if (path == null) { + throw new IllegalArgumentException(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + } + List stations = path.getVertexList(); List stationNames = stations.stream() .map(Station::getName) .collect(Collectors.toList()); @@ -49,8 +55,12 @@ private static int getTime(List stations) { } public static Result getMinTimeRoute(Station startStation, Station endStation) { - List stations = minTimePath.getPath(startStation, endStation).getVertexList(); + GraphPath path = minTimePath.getPath(startStation, endStation); + if (path == null) { + throw new IllegalArgumentException(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + } + List stations = path.getVertexList(); List stationNames = stations.stream() .map(Station::getName) .collect(Collectors.toList()); diff --git a/src/main/java/subway/domain/Station.java b/src/main/java/subway/domain/Station.java index 2a94b5c01..671a711d4 100644 --- a/src/main/java/subway/domain/Station.java +++ b/src/main/java/subway/domain/Station.java @@ -22,6 +22,11 @@ public boolean equals(Object object) { return Objects.equals(name, station.name); } + @Override + public int hashCode() { + return Objects.hashCode(name); + } + public String getName() { return name; } diff --git a/src/main/java/subway/view/OutputView.java b/src/main/java/subway/view/OutputView.java index 1506f2947..ecfc336cd 100644 --- a/src/main/java/subway/view/OutputView.java +++ b/src/main/java/subway/view/OutputView.java @@ -14,10 +14,10 @@ public static void printErrorMessage(IllegalArgumentException e) { public static void printRoute(Result result) { System.out.println("\n## 조회 결과"); System.out.println("[INFO] ---"); - System.out.printf("[INFO] 총 거리: %dkm\n", result.getDistance()); - System.out.printf("[INFO] 총 소요 시간: %d분\n", result.getTime()); + System.out.printf("[INFO] 총 거리: %dkm\n", result.distance()); + System.out.printf("[INFO] 총 소요 시간: %d분\n", result.time()); System.out.println("[INFO] ---"); - for (String station : result.getStations()) { + for (String station : result.stations()) { System.out.println("[INFO] " + station); } } From bd8554e9aec675a03a51fa63dc93da1ec0133f7c Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 17:25:01 +0900 Subject: [PATCH 12/15] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/subway/ApplicationTest.java | 104 +++++++++++++++ .../command/main/MainMenuOptionTest.java | 23 ++++ .../command/query/QueryMenuOptionTest.java | 23 ++++ .../subway/domain/RouteRepositoryTest.java | 119 ++++++++++++++++++ .../subway/domain/StationRepositoryTest.java | 42 +++++++ 5 files changed, 311 insertions(+) create mode 100644 src/test/java/subway/ApplicationTest.java create mode 100644 src/test/java/subway/command/main/MainMenuOptionTest.java create mode 100644 src/test/java/subway/command/query/QueryMenuOptionTest.java create mode 100644 src/test/java/subway/domain/RouteRepositoryTest.java create mode 100644 src/test/java/subway/domain/StationRepositoryTest.java diff --git a/src/test/java/subway/ApplicationTest.java b/src/test/java/subway/ApplicationTest.java new file mode 100644 index 000000000..ff9ce3a02 --- /dev/null +++ b/src/test/java/subway/ApplicationTest.java @@ -0,0 +1,104 @@ +package subway; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; + +import camp.nextstep.edu.missionutils.test.NsTest; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import subway.constant.ErrorMessage; + +class ApplicationTest extends NsTest { + + @Test + void 메인화면_기능_선택_오류() { + assertSimpleTest( + () -> { + runException("2"); + assertThat(output()).contains(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + ); + } + + @Test + void 경로조회화면_기능_선택_오류() { + assertSimpleTest( + () -> { + runException("1", "3"); + assertThat(output()).contains(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + ); + } + + @Test + void 없는_역_선택_오류() { + assertSimpleTest( + () -> { + runException("1", "1", "잠실역"); + assertThat(output()).contains(ErrorMessage.NO_EXIST_STATION.getErrorMessage()); + } + ); + } + + @ParameterizedTest + @MethodSource("minDistanceResultProvider") + void 최단거리_경로_구하기(String startStation, String endStation, int distance, int time) { + assertSimpleTest( + () -> { + runException("1", "1", startStation, endStation); + assertThat(output()).contains(distance + "km"); + assertThat(output()).contains(time + "분"); + } + ); + } + + static Stream minDistanceResultProvider() { + return Stream.of( + Arguments.of("교대역", "양재역", 4, 11), + Arguments.of("교대역", "강남역", 2, 3), + Arguments.of("교대역", "매봉역", 5, 12), + Arguments.of("교대역", "양재시민의숲역", 14, 14), + Arguments.of("양재역", "교대역", 4, 11), + Arguments.of("양재역", "역삼역", 4, 11), + Arguments.of("양재역", "남부터미널역", 6, 5), + Arguments.of("매봉역", "역삼역", 5, 12), + Arguments.of("매봉역", "강남역", 3, 9), + Arguments.of("강남역", "남부터미널역", 5, 5) + ); + } + + @ParameterizedTest + @MethodSource("minTimeResultProvider") + void 최소거리_경로_구하기(String startStation, String endStation, int distance, int time) { + assertSimpleTest( + () -> { + runException("1", "2", startStation, endStation); + assertThat(output()).contains(distance + "km"); + assertThat(output()).contains(time + "분"); + } + ); + } + + static Stream minTimeResultProvider() { + return Stream.of( + Arguments.of("교대역", "양재역", 9,7), + Arguments.of("교대역", "강남역", 2, 3), + Arguments.of("교대역", "매봉역", 10,8), + Arguments.of("교대역", "양재시민의숲역", 19, 10), + Arguments.of("양재역", "교대역", 9, 7), + Arguments.of("양재역", "역삼역", 4, 11), + Arguments.of("양재역", "남부터미널역", 6, 5), + Arguments.of("매봉역", "역삼역", 5, 12), + Arguments.of("매봉역", "강남역", 3, 9), + Arguments.of("강남역", "남부터미널역", 5, 5) + ); + } + + @Override + protected void runMain() { + Application.main(new String[]{}); + } +} \ No newline at end of file diff --git a/src/test/java/subway/command/main/MainMenuOptionTest.java b/src/test/java/subway/command/main/MainMenuOptionTest.java new file mode 100644 index 000000000..7aae35023 --- /dev/null +++ b/src/test/java/subway/command/main/MainMenuOptionTest.java @@ -0,0 +1,23 @@ +package subway.command.main; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import subway.constant.ErrorMessage; + +class MainMenuOptionTest { + + @Test + void 기능_선택_오류() { + assertThatThrownBy(() -> MainMenuOption.from("3")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + + @Test + void 기능_선택_정상() { + MainMenuOption option = MainMenuOption.from("1"); + assertThat(option).isEqualTo(MainMenuOption.QUERY); + } +} \ No newline at end of file diff --git a/src/test/java/subway/command/query/QueryMenuOptionTest.java b/src/test/java/subway/command/query/QueryMenuOptionTest.java new file mode 100644 index 000000000..079c26823 --- /dev/null +++ b/src/test/java/subway/command/query/QueryMenuOptionTest.java @@ -0,0 +1,23 @@ +package subway.command.query; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.Test; +import subway.constant.ErrorMessage; + +class QueryMenuOptionTest { + + @Test + void 기능_선택_오류() { + assertThatThrownBy(() -> QueryMenuOption.from("3")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.FORMAT_ERROR.getErrorMessage()); + } + + @Test + void 기능_선택_정상() { + QueryMenuOption option = QueryMenuOption.from("1"); + assertThat(option).isEqualTo(QueryMenuOption.DISTANCE); + } +} \ No newline at end of file diff --git a/src/test/java/subway/domain/RouteRepositoryTest.java b/src/test/java/subway/domain/RouteRepositoryTest.java new file mode 100644 index 000000000..3d27088e1 --- /dev/null +++ b/src/test/java/subway/domain/RouteRepositoryTest.java @@ -0,0 +1,119 @@ +package subway.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import subway.constant.ErrorMessage; + +class RouteRepositoryTest { + + @BeforeAll + static void setUpAll() { + List stations = Arrays.asList("교대역", "강남역", "역삼역", "남부터미널역", "양재역", "양재시민의숲역", "매봉역"); + List> stationPairs = Arrays.asList( + Arrays.asList("교대역", "강남역"), Arrays.asList("강남역", "역삼역"), + Arrays.asList("교대역", "남부터미널역"), Arrays.asList("남부터미널역", "양재역"), Arrays.asList("양재역", "매봉역"), + Arrays.asList("강남역", "양재역"), Arrays.asList("양재역", "양재시민의숲역") + ); + List> weightPairs = Arrays.asList( + Arrays.asList(2, 3), Arrays.asList(2, 3), + Arrays.asList(3, 2), Arrays.asList(6, 5), Arrays.asList(1, 1), + Arrays.asList(2, 8), Arrays.asList(10, 3) + ); + + for (String stationName : stations) { + Station station = Station.from(stationName); + StationRepository.addStation(station); + RouteRepository.addStation(station); + } + + for (int i = 0; i < stationPairs.size(); i++) { + Station start = StationRepository.getStation(stationPairs.get(i).get(0)); + Station end = StationRepository.getStation(stationPairs.get(i).get(1)); + int distance = weightPairs.get(i).get(0); + int time = weightPairs.get(i).get(1); + + RouteRepository.addRoute(start, end, distance, time); + } + } + + @ParameterizedTest + @MethodSource("minDistanceResultProvider") + void 최단거리_경로_테스트(Station startStation, Station endStation, int minDistance, int time, int size) { + Result result = RouteRepository.getMinDistanceRoute(startStation, endStation); + + assertThat(result.distance()).isEqualTo(minDistance); + assertThat(result.time()).isEqualTo(time); + assertThat(result.stations().size()).isEqualTo(size); + } + + static Stream minDistanceResultProvider() { + return Stream.of( + Arguments.of(Station.from("교대역"), Station.from("양재역"), 4, 11, 3), + Arguments.of(Station.from("교대역"), Station.from("강남역"), 2, 3, 2), + Arguments.of(Station.from("교대역"), Station.from("매봉역"), 5, 12, 4), + Arguments.of(Station.from("교대역"), Station.from("양재시민의숲역"), 14, 14, 4), + Arguments.of(Station.from("양재역"), Station.from("교대역"), 4, 11, 3), + Arguments.of(Station.from("양재역"), Station.from("역삼역"), 4, 11, 3), + Arguments.of(Station.from("양재역"), Station.from("남부터미널역"), 6, 5, 2), + Arguments.of(Station.from("매봉역"), Station.from("역삼역"), 5, 12, 4), + Arguments.of(Station.from("매봉역"), Station.from("강남역"), 3, 9, 3), + Arguments.of(Station.from("강남역"), Station.from("남부터미널역"), 5, 5, 3) + ); + } + + @ParameterizedTest + @MethodSource("minTimeResultProvider") + void 최소시간_경로_테스트(Station startStation, Station endStation, int minDistance, int time, int size) { + Result result = RouteRepository.getMinTimeRoute(startStation, endStation); + + assertThat(result.distance()).isEqualTo(minDistance); + assertThat(result.time()).isEqualTo(time); + assertThat(result.stations().size()).isEqualTo(size); + } + + static Stream minTimeResultProvider() { + return Stream.of( + Arguments.of(Station.from("교대역"), Station.from("양재역"), 9, 7, 3), + Arguments.of(Station.from("교대역"), Station.from("강남역"), 2, 3, 2), + Arguments.of(Station.from("교대역"), Station.from("매봉역"), 10, 8, 4), + Arguments.of(Station.from("교대역"), Station.from("양재시민의숲역"), 19, 10, 4), + Arguments.of(Station.from("양재역"), Station.from("교대역"), 9, 7, 3), + Arguments.of(Station.from("양재역"), Station.from("역삼역"), 4, 11, 3), + Arguments.of(Station.from("양재역"), Station.from("남부터미널역"), 6, 5, 2), + Arguments.of(Station.from("매봉역"), Station.from("역삼역"), 5, 12, 4), + Arguments.of(Station.from("매봉역"), Station.from("강남역"), 3, 9, 3), + Arguments.of(Station.from("강남역"), Station.from("남부터미널역"), 5, 5, 3) + ); + } + + @Test + void 두_역이_이어져_있지_않을때_오류() { + Station newFirstStation = Station.from("잠실역"); + Station newSecondStation = Station.from("송파역"); + + StationRepository.addStation(newFirstStation); + StationRepository.addStation(newSecondStation); + RouteRepository.addStation(newFirstStation); + RouteRepository.addStation(newSecondStation); + RouteRepository.addRoute(newFirstStation, newSecondStation, 3, 5); + + Station startStation = StationRepository.getStation("교대역"); + Station endStation = StationRepository.getStation("잠실역"); + + assertThatThrownBy(() -> RouteRepository.getMinDistanceRoute(startStation, endStation)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + assertThatThrownBy(() -> RouteRepository.getMinTimeRoute(startStation, endStation)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/subway/domain/StationRepositoryTest.java b/src/test/java/subway/domain/StationRepositoryTest.java new file mode 100644 index 000000000..a3da99e47 --- /dev/null +++ b/src/test/java/subway/domain/StationRepositoryTest.java @@ -0,0 +1,42 @@ +package subway.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import subway.constant.ErrorMessage; + +class StationRepositoryTest { + + @BeforeEach + void setUp() { + StationRepository.deleteAll(); + } + + @Test + void 등록된_역_찾기_성공() { + List stations = List.of(Station.from("강남역"), Station.from("역삼역")); + + for (Station station : stations) { + StationRepository.addStation(station); + } + + Station findStation = Station.from("강남역"); + assertThat(StationRepository.getStation("강남역")).isEqualTo(findStation); + } + + @Test + void 등록된_역_찾기_실패() { + List stations = List.of(Station.from("강남역"), Station.from("교대역")); + + for (Station station : stations) { + StationRepository.addStation(station); + } + + assertThatThrownBy(() -> StationRepository.getStation("역삼역")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NO_EXIST_STATION.getErrorMessage()); + } +} \ No newline at end of file From 55affd9bd99f4ea66a7060b8bcf5ac47c67524c3 Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 17:25:36 +0900 Subject: [PATCH 13/15] =?UTF-8?q?docs:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 8 ++++++++ gradle/wrapper/gradle-wrapper.properties | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ed9d9eeda..7f08631c1 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,13 @@ version '1.0-SNAPSHOT' repositories { mavenCentral() + maven { url 'https://jitpack.io' } +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } dependencies { @@ -14,6 +21,7 @@ dependencies { testImplementation('org.junit.jupiter:junit-jupiter:5.7.0') testImplementation('org.assertj:assertj-core:3.18.1') testImplementation('org.mockito:mockito-inline:3.6.0') + implementation ('com.github.woowacourse-projects:mission-utils:1.2.0') } test { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dcf98e9d0..bbb293702 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file From 2afee812c107b12a2977f1c0ff45ebd8b4e71f9f Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 19:58:02 +0900 Subject: [PATCH 14/15] =?UTF-8?q?feat:=20=EC=98=A4=EB=A5=98=EB=B0=9C?= =?UTF-8?q?=EC=83=9D=EC=8B=9C=20=EA=B8=B0=EB=8A=A5=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/main/impl/QueryCommand.java | 18 +++++++----------- .../command/query/impl/DistanceCommand.java | 19 +++++++------------ .../command/query/impl/TimeCommand.java | 19 +++++++------------ .../subway/controller/SubwayController.java | 4 ++-- .../java/subway/domain/StationRepository.java | 5 ----- src/main/java/subway/util/Retry.java | 14 +++++++++++++- 6 files changed, 36 insertions(+), 43 deletions(-) diff --git a/src/main/java/subway/command/main/impl/QueryCommand.java b/src/main/java/subway/command/main/impl/QueryCommand.java index 1b3878b71..9d0e5632b 100644 --- a/src/main/java/subway/command/main/impl/QueryCommand.java +++ b/src/main/java/subway/command/main/impl/QueryCommand.java @@ -20,18 +20,14 @@ public QueryCommand(QueryMenuCommandRegistry stationRegistry, Scanner scanner) { @Override public void execute() { - QueryMenuOption option = getOption(); + Retry.retryUntilSuccess(() -> { + QueryMenuOption option = InputParser.parseQueryMenuOption(InputView.readQueryMenuSelection(scanner)); - if (option.equals(QueryMenuOption.BACK)) { - return; - } + if (option.equals(QueryMenuOption.BACK)) { + return; + } - stationRegistry.execute(option); - } - - private QueryMenuOption getOption() { - return Retry.retryUntilSuccess(() -> - InputParser.parseQueryMenuOption(InputView.readQueryMenuSelection(scanner)) - ); + stationRegistry.execute(option); + }); } } diff --git a/src/main/java/subway/command/query/impl/DistanceCommand.java b/src/main/java/subway/command/query/impl/DistanceCommand.java index 14812f1a4..2dd71685b 100644 --- a/src/main/java/subway/command/query/impl/DistanceCommand.java +++ b/src/main/java/subway/command/query/impl/DistanceCommand.java @@ -7,7 +7,6 @@ import subway.domain.Station; import subway.service.SubwayService; import subway.util.InputParser; -import subway.util.Retry; import subway.view.InputView; import subway.view.OutputView; @@ -32,20 +31,16 @@ public void execute() { } private Station getStartStation() { - return Retry.retryUntilSuccess(() -> { - String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); - return service.getStation(stationName); - }); + String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); + return service.getStation(stationName); } private Station getEndStation(Station startStation) { - return Retry.retryUntilSuccess(() -> { - String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); - if (startStation.equals(service.getStation(stationName))) { - throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); - } - return service.getStation(stationName); - }); + String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); + if (startStation.equals(service.getStation(stationName))) { + throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); + } + return service.getStation(stationName); } private Result getResult(Station startStation, Station endStation) { diff --git a/src/main/java/subway/command/query/impl/TimeCommand.java b/src/main/java/subway/command/query/impl/TimeCommand.java index b607d700e..c3f5f7378 100644 --- a/src/main/java/subway/command/query/impl/TimeCommand.java +++ b/src/main/java/subway/command/query/impl/TimeCommand.java @@ -7,7 +7,6 @@ import subway.domain.Station; import subway.service.SubwayService; import subway.util.InputParser; -import subway.util.Retry; import subway.view.InputView; import subway.view.OutputView; @@ -32,20 +31,16 @@ public void execute() { } private Station getStartStation() { - return Retry.retryUntilSuccess(() -> { - String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); - return service.getStation(stationName); - }); + String stationName = InputParser.parseStation(InputView.readStartStation(scanner)); + return service.getStation(stationName); } private Station getEndStation(Station startStation) { - return Retry.retryUntilSuccess(() -> { - String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); - if (startStation.equals(service.getStation(stationName))) { - throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); - } - return service.getStation(stationName); - }); + String stationName = InputParser.parseStation(InputView.readEndStation(scanner)); + if (startStation.equals(service.getStation(stationName))) { + throw new IllegalArgumentException(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); + } + return service.getStation(stationName); } private Result getResult(Station startStation, Station endStation) { diff --git a/src/main/java/subway/controller/SubwayController.java b/src/main/java/subway/controller/SubwayController.java index e6afdb033..a70eae32b 100644 --- a/src/main/java/subway/controller/SubwayController.java +++ b/src/main/java/subway/controller/SubwayController.java @@ -24,7 +24,7 @@ public SubwayController(MainMenuCommandRegistry registry, SubwayService service, } public void run() throws IOException { - registerFileInfo(); + setInit(); while (true) { MainMenuOption option = readOption(); @@ -37,7 +37,7 @@ public void run() throws IOException { } } - private void registerFileInfo() throws IOException { + private void setInit() { List stations = Arrays.asList("교대역", "강남역", "역삼역", "남부터미널역", "양재역", "양재시민의숲역", "매봉역"); List> stationPairs = Arrays.asList( Arrays.asList("교대역", "강남역"), Arrays.asList("강남역", "역삼역"), diff --git a/src/main/java/subway/domain/StationRepository.java b/src/main/java/subway/domain/StationRepository.java index 9d9724b34..ef1137c4e 100644 --- a/src/main/java/subway/domain/StationRepository.java +++ b/src/main/java/subway/domain/StationRepository.java @@ -1,17 +1,12 @@ package subway.domain; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import subway.constant.ErrorMessage; public class StationRepository { private static final List stations = new ArrayList<>(); - public static List stations() { - return Collections.unmodifiableList(stations); - } - public static void addStation(Station station) { stations.add(station); } diff --git a/src/main/java/subway/util/Retry.java b/src/main/java/subway/util/Retry.java index 5f6e82a8a..8dddf25c1 100644 --- a/src/main/java/subway/util/Retry.java +++ b/src/main/java/subway/util/Retry.java @@ -5,7 +5,8 @@ public final class Retry { - private Retry() {} + private Retry() { + } public static T retryUntilSuccess(Supplier action) { while (true) { @@ -16,4 +17,15 @@ public static T retryUntilSuccess(Supplier action) { } } } + + public static void retryUntilSuccess(Runnable action) { + while (true) { + try { + action.run(); + return; + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e); + } + } + } } From 71a6e9788b22d0e57b153fd0110a4cefb5d220cc Mon Sep 17 00:00:00 2001 From: khcho96 Date: Sun, 4 Jan 2026 20:03:24 +0900 Subject: [PATCH 15/15] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/subway/ApplicationTest.java | 52 +++++++++++-------- .../command/main/MainMenuOptionTest.java | 2 +- .../command/query/QueryMenuOptionTest.java | 2 +- .../subway/domain/RouteRepositoryTest.java | 2 +- .../subway/domain/StationRepositoryTest.java | 2 +- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/test/java/subway/ApplicationTest.java b/src/test/java/subway/ApplicationTest.java index ff9ce3a02..7ddde4bcf 100644 --- a/src/test/java/subway/ApplicationTest.java +++ b/src/test/java/subway/ApplicationTest.java @@ -43,6 +43,16 @@ class ApplicationTest extends NsTest { ); } + @Test + void 출발역_도착역_동일_오류() { + assertSimpleTest( + () -> { + runException("1", "1", "교대역", "교대역"); + assertThat(output()).contains(ErrorMessage.SAME_START_END_STATION.getErrorMessage()); + } + ); + } + @ParameterizedTest @MethodSource("minDistanceResultProvider") void 최단거리_경로_구하기(String startStation, String endStation, int distance, int time) { @@ -57,16 +67,16 @@ class ApplicationTest extends NsTest { static Stream minDistanceResultProvider() { return Stream.of( - Arguments.of("교대역", "양재역", 4, 11), - Arguments.of("교대역", "강남역", 2, 3), - Arguments.of("교대역", "매봉역", 5, 12), - Arguments.of("교대역", "양재시민의숲역", 14, 14), - Arguments.of("양재역", "교대역", 4, 11), - Arguments.of("양재역", "역삼역", 4, 11), - Arguments.of("양재역", "남부터미널역", 6, 5), - Arguments.of("매봉역", "역삼역", 5, 12), - Arguments.of("매봉역", "강남역", 3, 9), - Arguments.of("강남역", "남부터미널역", 5, 5) + Arguments.of("교대역", "양재역", 4, 11), + Arguments.of("교대역", "강남역", 2, 3), + Arguments.of("교대역", "매봉역", 5, 12), + Arguments.of("교대역", "양재시민의숲역", 14, 14), + Arguments.of("양재역", "교대역", 4, 11), + Arguments.of("양재역", "역삼역", 4, 11), + Arguments.of("양재역", "남부터미널역", 6, 5), + Arguments.of("매봉역", "역삼역", 5, 12), + Arguments.of("매봉역", "강남역", 3, 9), + Arguments.of("강남역", "남부터미널역", 5, 5) ); } @@ -84,16 +94,16 @@ static Stream minDistanceResultProvider() { static Stream minTimeResultProvider() { return Stream.of( - Arguments.of("교대역", "양재역", 9,7), - Arguments.of("교대역", "강남역", 2, 3), - Arguments.of("교대역", "매봉역", 10,8), - Arguments.of("교대역", "양재시민의숲역", 19, 10), - Arguments.of("양재역", "교대역", 9, 7), - Arguments.of("양재역", "역삼역", 4, 11), - Arguments.of("양재역", "남부터미널역", 6, 5), - Arguments.of("매봉역", "역삼역", 5, 12), - Arguments.of("매봉역", "강남역", 3, 9), - Arguments.of("강남역", "남부터미널역", 5, 5) + Arguments.of("교대역", "양재역", 9, 7), + Arguments.of("교대역", "강남역", 2, 3), + Arguments.of("교대역", "매봉역", 10, 8), + Arguments.of("교대역", "양재시민의숲역", 19, 10), + Arguments.of("양재역", "교대역", 9, 7), + Arguments.of("양재역", "역삼역", 4, 11), + Arguments.of("양재역", "남부터미널역", 6, 5), + Arguments.of("매봉역", "역삼역", 5, 12), + Arguments.of("매봉역", "강남역", 3, 9), + Arguments.of("강남역", "남부터미널역", 5, 5) ); } @@ -101,4 +111,4 @@ static Stream minTimeResultProvider() { protected void runMain() { Application.main(new String[]{}); } -} \ No newline at end of file +} diff --git a/src/test/java/subway/command/main/MainMenuOptionTest.java b/src/test/java/subway/command/main/MainMenuOptionTest.java index 7aae35023..685f869e3 100644 --- a/src/test/java/subway/command/main/MainMenuOptionTest.java +++ b/src/test/java/subway/command/main/MainMenuOptionTest.java @@ -20,4 +20,4 @@ class MainMenuOptionTest { MainMenuOption option = MainMenuOption.from("1"); assertThat(option).isEqualTo(MainMenuOption.QUERY); } -} \ No newline at end of file +} diff --git a/src/test/java/subway/command/query/QueryMenuOptionTest.java b/src/test/java/subway/command/query/QueryMenuOptionTest.java index 079c26823..c135b0965 100644 --- a/src/test/java/subway/command/query/QueryMenuOptionTest.java +++ b/src/test/java/subway/command/query/QueryMenuOptionTest.java @@ -20,4 +20,4 @@ class QueryMenuOptionTest { QueryMenuOption option = QueryMenuOption.from("1"); assertThat(option).isEqualTo(QueryMenuOption.DISTANCE); } -} \ No newline at end of file +} diff --git a/src/test/java/subway/domain/RouteRepositoryTest.java b/src/test/java/subway/domain/RouteRepositoryTest.java index 3d27088e1..8b0a9b446 100644 --- a/src/test/java/subway/domain/RouteRepositoryTest.java +++ b/src/test/java/subway/domain/RouteRepositoryTest.java @@ -116,4 +116,4 @@ static Stream minTimeResultProvider() { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(ErrorMessage.NO_CONNECTED_STATIONS.getErrorMessage()); } -} \ No newline at end of file +} diff --git a/src/test/java/subway/domain/StationRepositoryTest.java b/src/test/java/subway/domain/StationRepositoryTest.java index a3da99e47..5f4757734 100644 --- a/src/test/java/subway/domain/StationRepositoryTest.java +++ b/src/test/java/subway/domain/StationRepositoryTest.java @@ -39,4 +39,4 @@ void setUp() { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(ErrorMessage.NO_EXIST_STATION.getErrorMessage()); } -} \ No newline at end of file +}