Skip to content
Open

Final #115

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 지하철 노선도 경로 조회

## 기능 요구사항

- 경로 조회 기능을 제공한다.
- 경로 조회 기준은 최단 거리 / 최소 시간 의 두 가지 기준을 사용한다.
- 경로 조회 기준을 선택한다.
- 잘못된 메뉴 입력시 예외 발생
- 1, 2를 제외한 모든 입력에서 예외 발생
- 출발열과 도착역을 입력하면 경로 조회 기준에 따라 조회 결과를 출력한다.
- [EXCEPTION] 출발 역과 도착 역이 같으면 예외 발생
68 changes: 67 additions & 1 deletion src/main/java/subway/Application.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,76 @@
package subway;

import java.util.Scanner;
import subway.controller.exception.RetryHandler;
import subway.domain.PathResult;
import subway.domain.Station;
import subway.domain.SubwayService;
import subway.domain.repository.RepositoryConfig;
import subway.domain.repository.StationRepository;
import subway.view.View;

public class Application {
private static final View view = new View();
private static final RetryHandler handler = new RetryHandler();
private static SubwayService subwayService;

public static void main(String[] args) {
final Scanner scanner = new Scanner(System.in);
// TODO: 프로그램 구현
RepositoryConfig.initRepository();
subwayService = new SubwayService();

handler.runOrRetry(() -> run(scanner));
}

public static void run(Scanner scanner){
while(true) {
view.printMenuView();
String input = scanner.nextLine();
if (input.equals("Q")) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리터럴 및 입력을 비교할 때, "Q".equals(input)과 같은 방식으로 코드를 작성하면 어떨까 합니다.

Scanner에 의해 입력 받는 값이 null이 될 수는 없기 때문에 해당 코드에서 NPE가 발생하진 않겠으나,

Scanner가 아닌 다른 null이 될 가능성이 있는 String 타입의 변수를 리터럴과 비교 연산할 때, NPE를 막아줄 수 있겠습니다.

이러한 규칙을 코딩 표준으로 만들어서 항상 literal.equals(variable) 구조로 작성한다면, 일관적인 스타일로 코드를 작성하면서 NPE을 방지할 수 있을 것 같습니다.

return;
}
if (input.equals("1")) {
handler.runOrRetry(() -> findPath(scanner));
continue;
}
throw new IllegalArgumentException("잘못된 메뉴 입력입니다.");
}
}

private static void findPath(Scanner scanner) {
view.printPathMenu();
String input = scanner.nextLine();
if (input.equals("B")) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여러 Command를 모두 String Literal 형태로 Controller 내에서 관리하고 있는데, 각 Command의 카테고리에 맞게 enum을 별도로 생성하고 검증해주면 좋을 것 같습니다.

이점은 각 Command에 대한 테스트를 작성하기에 유리하고, 여러 파일에서 문자열 리터럴을 관리하는 것보다 유지 보수 향상, 실수 방지 및 파악에 용이해진다는 점이 있다고 생각합니다.

enum만 들어가도 각 Command에 대한 상태를 모조리 볼 수 있으니까요.

@june-777 님의 ApplicationStatus 등을 참고하시면 도움이 될 것 같습니다.

시간 단축을 위해 작성하셨을 수도 있을 것 같은데, Commandenum화 하는게 좀 더 좋은 인상을 줄 수 있을 것 같아서 처음에 이렇게 작성하고 일단 작동 시킨 뒤에 리팩토링 하는 방향도 좋을 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다,,, 리팩토링을 하고싶었지만 시간이 부족해 하지 못했던 부분이네요 ㅠ

return;
}
if (input.equals("1") || input.equals("2")) {
_findPath(scanner, input);
return;
}

//todo
throw new IllegalArgumentException("잘못된 메뉴 입력입니다.");
}

private static void _findPath(Scanner scanner, String input) {
view.guideStartStation();
Station start = getStation(scanner);
view.guideEndStation();
Station end = getStation(scanner);
PathResult result = null;
if(input.equals("1")){
result = subwayService.findShortestPath(start, end);
}
if (input.equals("2")) {
result = subwayService.findFastestPath(start, end);
}
view.printPathResult(result);
}

private static Station getStation(Scanner scanner) {
String stationName = scanner.nextLine();
return StationRepository.getStationByName(stationName);
}


}
37 changes: 37 additions & 0 deletions src/main/java/subway/controller/SubwayController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package subway.controller;

import subway.view.View;

public class SubwayController {
private final View view;

public SubwayController(View view) {
this.view = view;
}
//todo Button 사용 가능할지 고민해 보기

/*
경로 기준은 경로 탐색 전략을 결정해 준다.
이후 시작, 끝 점을 사용해 경로를 탐색한다.


1. 전략 선택 (최단 거리 / 최단 시간)
2. 시작 / 끝 역 선택
3. 경로 구함
4. 결과 출력
- 총 거리
- 총 시간
- 경로 내 모든 역 (이건 라이브러리의 반환 결과 사용하면 됨)

-> 총 거리, 총 시간은 라이브러리 반환 결과의 경로를 이용해 구하는 것이 맞는 것 같음
-> A-B-C-D 경로일 경우
각 `Line`을 순회하면서 `A-B` 경로가 존재하는지 확인 후 거리와 시간을 가져온다.

그럼 경로 정보는 `Line`이 가지고 있어야 하겠네

StationRepository 는 그냥 입력받은 역들이 존재하는 역인지 판단하는 역할 이상은 못할 것 같음

*/


}
33 changes: 33 additions & 0 deletions src/main/java/subway/controller/exception/RetryHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package subway.controller.exception;

import java.util.function.Supplier;
import subway.view.View;

public class RetryHandler {
private final View view = new View();

public <T> T getOrRetry(Supplier<T> supplier){
while(true){
try {
return supplier.get();
} catch (IllegalArgumentException e){
view.printException(e.getMessage());
} finally {
view.printLine();
}
}
}

public void runOrRetry(Runnable runnable){
while(true){
try {
runnable.run();
return;
} catch (IllegalArgumentException e){
view.printException(e.getMessage());
} finally {
view.printLine();
}
}
}
}
20 changes: 20 additions & 0 deletions src/main/java/subway/domain/Line.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package subway.domain;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public class Line {
private String name;
private final Set<UnitPath> paths = new HashSet<>();

public Line(String name) {
this.name = name;
Expand All @@ -12,4 +19,17 @@ public String getName() {
}

// 추가 기능 구현
public void addPath(UnitPath path){
paths.add(path);
}

public Optional<UnitPath> getPathOf(Station start, Station end) {
return paths.stream()
.filter(path -> path.isPathOf(start, end))
.findFirst();
}

public Set<UnitPath> getPaths() {
return Collections.unmodifiableSet(paths);
}
}
27 changes: 27 additions & 0 deletions src/main/java/subway/domain/PathResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package subway.domain;

import java.util.List;

public class PathResult {
private final List<Station> stations;
private final int time;
private final int distance;

public PathResult(List<Station> stations, int time, int distance) {
this.stations = stations;
this.time = time;
this.distance = distance;
}

public List<Station> getStations() {
return stations;
}

public int getTime() {
return time;
}

public int getDistance() {
return distance;
}
}
29 changes: 29 additions & 0 deletions src/main/java/subway/domain/Station.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package subway.domain;

import java.util.Objects;

public class Station {
private String name;

Expand All @@ -12,4 +14,31 @@ public String getName() {
}

// 추가 기능 구현
public boolean isName(String name) {
return this.name.equals(name);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Station station = (Station) o;
return Objects.equals(name, station.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}

@Override
public String toString() {
return "Station{" +
"name='" + name + '\'' +
'}';
}
}
32 changes: 32 additions & 0 deletions src/main/java/subway/domain/SubwayService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package subway.domain;

import org.jgrapht.GraphPath;
import org.jgrapht.graph.DefaultWeightedEdge;
import subway.domain.pathFinder.DistancePathFinder;
import subway.domain.pathFinder.StationPathFinder;
import subway.domain.pathFinder.TimePathFinder;
import subway.domain.repository.PathRepository;

public class SubwayService {
private static final StationPathFinder timePathFinder = new TimePathFinder();
private static final StationPathFinder distanceFinder = new DistancePathFinder();

public PathResult findShortestPath(Station start, Station end) {
GraphPath<Station, DefaultWeightedEdge> path = findPath(distanceFinder, start, end);
int time = PathRepository.getTotalTime(path.getVertexList());
return new PathResult(path.getVertexList(), time, (int)path.getWeight());
}

public PathResult findFastestPath(Station start, Station end) {
GraphPath<Station, DefaultWeightedEdge> path = findPath(timePathFinder, start, end);
int distance = PathRepository.getTotalDistance(path.getVertexList());
return new PathResult(path.getVertexList(), (int) path.getWeight(), distance);
}
private GraphPath<Station, DefaultWeightedEdge> findPath(StationPathFinder pathFinder, Station start, Station end) {
if(start.equals(end)){
throw new IllegalArgumentException("출발역과 도착역이 동일합니다.");
}
return pathFinder.findPath(start, end);
}

}
45 changes: 45 additions & 0 deletions src/main/java/subway/domain/UnitPath.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package subway.domain;

import java.util.Set;

public class UnitPath {
private final Station start;
private final Station end;

//todo 아래 두 값 래핑
private final int time;
private final int distance;

public UnitPath(Station start, Station end, int time, int distance) {
this.start = start;
this.end = end;
this.time = time;
this.distance = distance;
}

public boolean isPathOf(Station start, Station end) {
if (this.start.equals(start) && this.end.equals(end)) {
return true;
}
if (this.start.equals(end) && this.end.equals(start)) {
return true;
}
return false;
}

public Station getEnd() {
return end;
}

public Station getStart() {
return start;
}

public int getDistance() {
return distance;
}

public int getTime() {
return time;
}
}
15 changes: 15 additions & 0 deletions src/main/java/subway/domain/pathFinder/DistancePathFinder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package subway.domain.pathFinder;

import subway.domain.repository.LineRepository;
import subway.domain.UnitPath;

public class DistancePathFinder extends StationPathFinder{
public DistancePathFinder() {
super();
}

@Override
protected int getCost(UnitPath path) {
return path.getDistance();
}
}
Loading