From 45489c4d7b5a38f34bab429f19db10dbdc2acfd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 15 Aug 2022 17:46:03 +0900 Subject: [PATCH 01/14] =?UTF-8?q?test=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EB=85=B8=EC=84=A0=20=EC=8B=9C=EA=B0=84=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=9D=B8=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance/line/LineAcceptanceTest.java | 50 ++++++++++++++++++- .../subway/acceptance/line/LineSteps.java | 44 ++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/test/java/nextstep/subway/acceptance/line/LineAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/line/LineAcceptanceTest.java index 73d81a10c..d53319f2a 100644 --- a/src/test/java/nextstep/subway/acceptance/line/LineAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/line/LineAcceptanceTest.java @@ -7,7 +7,14 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; -import static nextstep.subway.acceptance.line.LineSteps.*; +import java.time.LocalTime; + +import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_검증; +import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_목록_조회_요청; +import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_삭제_요청; +import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_생성_요청; +import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_수정_요청; +import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_조회_요청; import static org.assertj.core.api.Assertions.assertThat; @DisplayName("지하철 노선 관리 기능") @@ -105,4 +112,45 @@ void deleteLine() { // then assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); } + + /** + * When 지하철 노선 정보(첫차 시간, 막차 시간, 간격)을 추가하면 + * Then 지하철 노선 정보가 추가된다. + * When 추가된 지하철 노선을 조회하면 + * Then 추가된 지하철 노선 정보가 조회된다. + * When 추가된 지하철 노선의 첫차 시간, 막차 시간, 간격을 수정하면 + * Then 지하철 노선의 정보가 수정된다. + * When 추가된 지하철 노선을 삭제하면 + * Then 지하철 노선이 삭제된다. + */ + @DisplayName("지하철 노선 첫차, 막차, 간격 관리") + @Test + void saveLineWithTimes() { + // when + ExtractableResponse createResponse = 지하철_노선_생성_요청(관리자, "2호선", "green", LocalTime.of(05, 00, 00), LocalTime.of(23, 00, 00), 10); + + // then + assertThat(createResponse.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + + // when + ExtractableResponse findResponse = 지하철_노선_조회_요청(createResponse); + + // then + 지하철_노선_검증(findResponse, "2호선", "green", "05:00:00", "23:00:00", 10); + + // when + ExtractableResponse updateResponse = 지하철_노선_수정_요청(관리자, createResponse, "red", LocalTime.of(06, 00, 00), LocalTime.of(23, 30, 00), 5); + + // then + assertThat(updateResponse.statusCode()).isEqualTo(HttpStatus.OK.value()); + + ExtractableResponse afterUpdateResponse = 지하철_노선_조회_요청(createResponse); + 지하철_노선_검증(afterUpdateResponse, "2호선", "red", "06:00:00", "23:30:00", 5); + + // when + ExtractableResponse deleteResponse = 지하철_노선_삭제_요청(관리자, createResponse); + + // then + assertThat(deleteResponse.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } } diff --git a/src/test/java/nextstep/subway/acceptance/line/LineSteps.java b/src/test/java/nextstep/subway/acceptance/line/LineSteps.java index 54ba6530c..63d85a0a8 100644 --- a/src/test/java/nextstep/subway/acceptance/line/LineSteps.java +++ b/src/test/java/nextstep/subway/acceptance/line/LineSteps.java @@ -5,9 +5,12 @@ import nextstep.subway.acceptance.AcceptanceTestSteps; import org.springframework.http.MediaType; +import java.time.LocalTime; import java.util.HashMap; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; + public class LineSteps extends AcceptanceTestSteps { public static ExtractableResponse 지하철_노선_생성_요청(String token, String name, String color) { Map params = new HashMap<>(); @@ -20,6 +23,20 @@ public class LineSteps extends AcceptanceTestSteps { .then().log().all().extract(); } + public static ExtractableResponse 지하철_노선_생성_요청(String token, String name, String color, LocalTime startTime, LocalTime endTime, int intervalTime) { + Map params = new HashMap<>(); + params.put("name", name); + params.put("color", color); + params.put("startTime", startTime); + params.put("endTime", endTime); + params.put("intervalTime", intervalTime); + return given(token) + .body(params) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/lines") + .then().log().all().extract(); + } + public static ExtractableResponse 지하철_노선_목록_조회_요청() { return given() .when().get("/lines") @@ -56,6 +73,19 @@ public class LineSteps extends AcceptanceTestSteps { .then().log().all().extract(); } + public static ExtractableResponse 지하철_노선_수정_요청(String token, ExtractableResponse response, + String color, LocalTime startTime, LocalTime endTime, int intervalTime) { + Map params = new HashMap<>(); + params.put("color", color); + params.put("startTime", startTime); + params.put("endTime", endTime); + params.put("intervalTime", intervalTime); + return given(token) + .body(params) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().put(response.header("Location")) + .then().log().all().extract(); + } public static ExtractableResponse 지하철_노선_삭제_요청(String token, String location) { ExtractableResponse response = given(token) @@ -64,6 +94,12 @@ public class LineSteps extends AcceptanceTestSteps { return response; } + public static ExtractableResponse 지하철_노선_삭제_요청(String token, ExtractableResponse response) { + return given(token) + .when().delete(response.header("Location")) + .then().log().all().extract(); + } + public static ExtractableResponse 지하철_노선에_지하철_구간_생성_요청(String token, Long lineId, Map params) { return given(token) .contentType(MediaType.APPLICATION_JSON_VALUE) @@ -77,4 +113,12 @@ public class LineSteps extends AcceptanceTestSteps { .when().delete("/lines/{lineId}/sections?stationId={stationId}", lineId, stationId) .then().log().all().extract(); } + + public static void 지하철_노선_검증(ExtractableResponse response, String name, String color, String startTime, String endTime, int intervalTime) { + assertThat(response.jsonPath().getString("name")).isEqualTo(name); + assertThat(response.jsonPath().getString("color")).isEqualTo(color); + assertThat(response.jsonPath().getString("startTime")).isEqualTo(startTime); + assertThat(response.jsonPath().getString("endTime")).isEqualTo(endTime); + assertThat(response.jsonPath().getInt("intervalTime")).isEqualTo(intervalTime); + } } From 22dbe1ded5f0e1247ba26d9520bea8d9dec8f5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 15 Aug 2022 17:46:31 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EB=85=B8=EC=84=A0=20=EC=8B=9C=EA=B0=84=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/applicaion/LineService.java | 11 ++- .../subway/applicaion/dto/LineRequest.java | 35 ++++++- .../subway/applicaion/dto/LineResponse.java | 39 +++++++- .../java/nextstep/subway/domain/Line.java | 94 ++++++++++++++++++- 4 files changed, 173 insertions(+), 6 deletions(-) diff --git a/src/main/java/nextstep/subway/applicaion/LineService.java b/src/main/java/nextstep/subway/applicaion/LineService.java index f2ad53960..aba275055 100644 --- a/src/main/java/nextstep/subway/applicaion/LineService.java +++ b/src/main/java/nextstep/subway/applicaion/LineService.java @@ -27,7 +27,7 @@ public LineService(LineRepository lineRepository, StationService stationService) @Transactional public LineResponse saveLine(LineRequest request) { - Line line = lineRepository.save(new Line(request.getName(), request.getColor(), request.getAdditionalFare())); + Line line = lineRepository.save(request.toLineEntity()); if (request.getUpStationId() != null && request.getDownStationId() != null && request.getDistance() != 0) { Station upStation = stationService.findById(request.getUpStationId()); Station downStation = stationService.findById(request.getDownStationId()); @@ -62,7 +62,14 @@ public Line findById(Long id) { @Transactional public void updateLine(Long id, LineRequest lineRequest) { Line line = findById(id); - line.update(lineRequest.getName(), lineRequest.getColor(), line.getAdditionalFare()); + line.update( + lineRequest.getName(), + lineRequest.getColor(), + lineRequest.getAdditionalFare(), + lineRequest.getStartTime(), + lineRequest.getEndTime(), + lineRequest.getIntervalTime() + ); } @Transactional diff --git a/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java b/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java index ea1e1bc7b..6df2ec542 100644 --- a/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java +++ b/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java @@ -1,8 +1,15 @@ package nextstep.subway.applicaion.dto; +import nextstep.subway.domain.Line; + +import java.time.LocalTime; + public class LineRequest { private String name; private String color; + private LocalTime startTime; + private LocalTime endTime; + private int intervalTime; private int additionalFare; private Long upStationId; private Long downStationId; @@ -12,9 +19,12 @@ public class LineRequest { public LineRequest() { } - public LineRequest(String name, String color, int additionalFare, Long upStationId, Long downStationId, int distance, int duration) { + public LineRequest(String name, String color, LocalTime startTime, LocalTime endTime, int intervalTime, int additionalFare, Long upStationId, Long downStationId, int distance, int duration) { this.name = name; this.color = color; + this.startTime = startTime; + this.endTime = endTime; + this.intervalTime = intervalTime; this.additionalFare = additionalFare; this.upStationId = upStationId; this.downStationId = downStationId; @@ -49,4 +59,27 @@ public int getDistance() { public int getDuration() { return duration; } + + public LocalTime getStartTime() { + return startTime; + } + + public LocalTime getEndTime() { + return endTime; + } + + public int getIntervalTime() { + return intervalTime; + } + + public Line toLineEntity() { + return Line.builder() + .name(this.name) + .color(this.color) + .additionalFare(this.additionalFare) + .startTime(this.startTime) + .endTime(this.endTime) + .intervalTime(this.intervalTime) + .build(); + } } diff --git a/src/main/java/nextstep/subway/applicaion/dto/LineResponse.java b/src/main/java/nextstep/subway/applicaion/dto/LineResponse.java index 87fc496ab..191d40734 100644 --- a/src/main/java/nextstep/subway/applicaion/dto/LineResponse.java +++ b/src/main/java/nextstep/subway/applicaion/dto/LineResponse.java @@ -2,26 +2,43 @@ import nextstep.subway.domain.Line; +import java.time.LocalTime; import java.util.List; import java.util.stream.Collectors; +import static java.time.format.DateTimeFormatter.ISO_TIME; + public class LineResponse { private Long id; private String name; private String color; + private LocalTime startTime; + private LocalTime endTime; + private int intervalTime; private List stations; public static LineResponse of(Line line) { List stations = line.getStations().stream() .map(StationResponse::of) .collect(Collectors.toList()); - return new LineResponse(line.getId(), line.getName(), line.getColor(), stations); + return new LineResponse( + line.getId(), + line.getName(), + line.getColor(), + line.getStartTime(), + line.getEndTime(), + line.getIntervalTime(), + stations + ); } - public LineResponse(Long id, String name, String color, List stations) { + public LineResponse(Long id, String name, String color, LocalTime startTime, LocalTime endTime, int intervalTime, List stations) { this.id = id; this.name = name; this.color = color; + this.startTime = startTime; + this.endTime = endTime; + this.intervalTime = intervalTime; this.stations = stations; } @@ -37,6 +54,24 @@ public String getColor() { return color; } + public String getStartTime() { + if (this.startTime == null) { + return null; + } + return startTime.format(ISO_TIME); + } + + public String getEndTime() { + if (this.startTime == null) { + return null; + } + return endTime.format(ISO_TIME); + } + + public int getIntervalTime() { + return intervalTime; + } + public List getStations() { return stations; } diff --git a/src/main/java/nextstep/subway/domain/Line.java b/src/main/java/nextstep/subway/domain/Line.java index cd95235e1..e15f9b07c 100644 --- a/src/main/java/nextstep/subway/domain/Line.java +++ b/src/main/java/nextstep/subway/domain/Line.java @@ -7,6 +7,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import java.time.LocalTime; import java.util.List; @Entity @@ -17,6 +18,9 @@ public class Line { private String name; private String color; private int additionalFare; + private LocalTime startTime; + private LocalTime endTime; + private int intervalTime; @Embedded private Sections sections = new Sections(); @@ -29,9 +33,20 @@ public Line(String name, String color) { } public Line(String name, String color, int additionalFare) { + this(name, color, additionalFare, null, null, 0); + } + + public Line(String name, String color, int additionalFare, LocalTime startTime, LocalTime endTime, int intervalTime) { this.name = name; this.color = color; this.additionalFare = additionalFare; + this.startTime = startTime; + this.endTime = endTime; + this.intervalTime = intervalTime; + } + + public static LineBuilder builder() { + return new LineBuilder(); } public Long getId() { @@ -46,6 +61,18 @@ public String getColor() { return color; } + public LocalTime getStartTime() { + return startTime; + } + + public LocalTime getEndTime() { + return endTime; + } + + public int getIntervalTime() { + return intervalTime; + } + public int getAdditionalFare() { return additionalFare; } @@ -54,7 +81,7 @@ public List
getSections() { return sections.getSections(); } - public void update(String name, String color, int additionalFare) { + public void update(String name, String color, int additionalFare, LocalTime startTime, LocalTime endTime, int intervalTime) { if (name != null) { this.name = name; } @@ -62,6 +89,13 @@ public void update(String name, String color, int additionalFare) { this.color = color; } this.additionalFare = additionalFare; + if (startTime != null) { + this.startTime = startTime; + } + if (endTime != null) { + this.endTime = endTime; + } + this.intervalTime = intervalTime; } public void addSection(Station upStation, Station downStation, int distance, int duration) { @@ -83,4 +117,62 @@ public void deleteSection(Station station) { sections.delete(station); } + public static class LineBuilder { + private String name; + private String color; + private int additionalFare; + private LocalTime startTime; + private LocalTime endTime; + private int intervalTime; + + public LineBuilder name(String name) { + this.name = name; + return this; + } + + public LineBuilder color(String color) { + this.color = color; + return this; + } + + public LineBuilder additionalFare(int additionalFare) { + this.additionalFare = additionalFare; + return this; + } + + public LineBuilder startTime(LocalTime startTime) { + this.startTime = startTime; + return this; + } + + public LineBuilder endTime(LocalTime endTime) { + this.endTime = endTime; + return this; + } + + public LineBuilder intervalTime(int intervalTime) { + this.intervalTime = intervalTime; + return this; + } + + public Line build() { + Line line = new Line(); + if (this.name != null) { + line.name = this.name; + } + if (this.color != null) { + line.color = this.color; + } + if (this.startTime != null) { + line.startTime = this.startTime; + } + if (this.endTime != null) { + line.endTime = this.endTime; + } + line.additionalFare = this.additionalFare; + line.intervalTime = this.intervalTime; + return line; + } + } + } From 9de7f0498829b47aea28be99fd9f0509566af349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 15 Aug 2022 21:19:41 +0900 Subject: [PATCH 03/14] =?UTF-8?q?test=20=EA=B0=80=EC=9E=A5=20=EB=B9=A0?= =?UTF-8?q?=EB=A5=B8=20=EB=8F=84=EC=B0=A9=20=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=9D=B8=EC=88=98=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/nextstep/subway/domain/PathType.java | 6 ++ .../nextstep/support/entity/Formatters.java | 7 ++ .../acceptance/path/PathAcceptanceTest.java | 99 +++++++++++++++++-- .../subway/acceptance/path/PathSteps.java | 23 +++++ 4 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 src/main/java/nextstep/support/entity/Formatters.java diff --git a/src/main/java/nextstep/subway/domain/PathType.java b/src/main/java/nextstep/subway/domain/PathType.java index faaaa663e..6a87c9745 100644 --- a/src/main/java/nextstep/subway/domain/PathType.java +++ b/src/main/java/nextstep/subway/domain/PathType.java @@ -12,6 +12,12 @@ public int getEdgeWeight(Section section) { public int getEdgeWeight(Section section) { return section.getDuration(); } + }, + ARRIVAL_TIME { + @Override + public int getEdgeWeight(Section section) { + return 0; + } }; public abstract int getEdgeWeight(Section section); diff --git a/src/main/java/nextstep/support/entity/Formatters.java b/src/main/java/nextstep/support/entity/Formatters.java new file mode 100644 index 000000000..bafd33f8f --- /dev/null +++ b/src/main/java/nextstep/support/entity/Formatters.java @@ -0,0 +1,7 @@ +package nextstep.support.entity; + +import java.time.format.DateTimeFormatter; + +public interface Formatters { + DateTimeFormatter DATE_TIME_PATH = DateTimeFormatter.ofPattern("yyyyMMddHHmm"); +} diff --git a/src/test/java/nextstep/subway/acceptance/path/PathAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/path/PathAcceptanceTest.java index 43033c1e7..1ede16ba2 100644 --- a/src/test/java/nextstep/subway/acceptance/path/PathAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/path/PathAcceptanceTest.java @@ -4,23 +4,32 @@ import io.restassured.response.Response; import nextstep.subway.acceptance.AcceptanceTest; import nextstep.subway.acceptance.line.LineSteps; +import nextstep.support.entity.Formatters; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.Map; +import static java.time.format.DateTimeFormatter.ISO_TIME; import static nextstep.subway.acceptance.AcceptanceTestSteps.given; import static nextstep.subway.acceptance.line.LineSteps.지하철_노선에_지하철_구간_생성_요청; import static nextstep.subway.acceptance.member.MemberSteps.로그인_되어_있음; import static nextstep.subway.acceptance.path.PathSteps.경로_조회_응답_검증; +import static nextstep.subway.acceptance.path.PathSteps.두_역의_가장_빠른_도착_경로_조회를_요청; import static nextstep.subway.acceptance.path.PathSteps.두_역의_최단_거리_경로_조회를_요청; import static nextstep.subway.acceptance.path.PathSteps.두_역의_최소_시간_경로_조회를_요청; import static nextstep.subway.acceptance.station.StationSteps.지하철역_생성_요청; +import static nextstep.support.entity.Formatters.DATE_TIME_PATH; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.in; @DisplayName("지하철 경로 검색") class PathAcceptanceTest extends AcceptanceTest { @@ -437,21 +446,84 @@ void findPathWith19AgeByDistance() { } } + @DisplayName("가장 빠른 도착 경로 기준으로") + @Nested + class Context_with_Arrival_Time { + Long 서초역; + Long 강남역; + Long 역삼역; + Long 선릉역; + Long 양재역; + + Long 이호선; + Long 신분당선; + + @BeforeEach + void setUp() { + 서초역 = 지하철역_생성_요청(관리자, "서초역").jsonPath().getLong("id"); + 강남역 = 지하철역_생성_요청(관리자, "강남역").jsonPath().getLong("id"); + 역삼역 = 지하철역_생성_요청(관리자, "역삼역").jsonPath().getLong("id"); + 선릉역 = 지하철역_생성_요청(관리자, "선릉역").jsonPath().getLong("id"); + 양재역 = 지하철역_생성_요청(관리자, "양재역").jsonPath().getLong("id"); + + 이호선 = 지하철_노선_생성_요청("2호선", "green", 서초역, 강남역, 5, 3, LocalTime.of(5, 0), LocalTime.of(23, 0), 10); + 지하철_노선에_지하철_구간_생성_요청(관리자, 이호선, createSectionCreateParams(역삼역, 선릉역, 7, 4)); + 지하철_노선에_지하철_구간_생성_요청(관리자, 이호선, createSectionCreateParams(역삼역, 선릉역, 4, 3)); + + 신분당선 = 지하철_노선_생성_요청("신분당선", "red", 강남역, 양재역, 6, 4, 900, LocalTime.of(5, 0), LocalTime.of(23, 0), 20); + } + + @DisplayName("환승이 없는 서초역에서 선릉역까지 10:00 기준으로 조회하면, 도착 시간은 10:10 이다.") + @Test + void findPathByArrivalTimeOfSameLine() { + // given + LocalDateTime dateTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(10, 00, 00)); + + // when + ExtractableResponse response = 두_역의_가장_빠른_도착_경로_조회를_요청(given(관리자), 서초역, 선릉역, dateTime.format(DATE_TIME_PATH)); + + // then + final int 거리_16km = 16; + final int 시간_10분 = 10; + final int 요금_1350원 = 1350; + final String 도착_시간_오늘_10시_10분 = dateTime.plusMinutes(10).format(DATE_TIME_PATH); + 경로_조회_응답_검증(response, 거리_16km, 시간_10분, 요금_1350원, 도착_시간_오늘_10시_10분, 서초역, 강남역, 역삼역, 선릉역); + } + + @DisplayName("추가요금이 있는 신분당선이 포함된 환승이 있는 서초역에서 양재역까지 10:00 기준으로 조회하면, 도착 시간은 10:24 이다.") + @Test + void findPathByArrivalTimeForTransfer() { + // given + LocalDateTime dateTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(10, 00, 00)); + + // when + ExtractableResponse response = 두_역의_가장_빠른_도착_경로_조회를_요청(given(관리자), 서초역, 양재역, dateTime.format(DATE_TIME_PATH)); + + // then + final int 거리_11km = 11; + final int 시간_10분 = 10; + final int 요금_2150원 = 2150; + final String 도착_시간_오늘_10시_24분 = dateTime.plusMinutes(24).format(DATE_TIME_PATH); + 경로_조회_응답_검증(response, 거리_11km, 시간_10분, 요금_2150원, 도착_시간_오늘_10시_24분, 서초역, 강남역, 양재역); + } + + } + } private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance, int duration) { - Map lineCreateParams = new HashMap<>(); - lineCreateParams.put("name", name); - lineCreateParams.put("color", color); - lineCreateParams.put("upStationId", upStation + ""); - lineCreateParams.put("downStationId", downStation + ""); - lineCreateParams.put("distance", distance + ""); - lineCreateParams.put("duration", duration + ""); + return 지하철_노선_생성_요청(name, color, upStation, downStation, distance, duration, 0); + } - return LineSteps.지하철_노선_생성_요청(관리자, lineCreateParams).jsonPath().getLong("id"); + private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance, int duration, int additionalFare) { + return 지하철_노선_생성_요청(name, color, upStation, downStation, distance, duration, additionalFare, null, null, 0); + } + + private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance, int duration, LocalTime startTime, LocalTime endTime, int intervalTime) { + return 지하철_노선_생성_요청(name, color, upStation, downStation, distance, duration, 0, startTime, endTime, intervalTime); } - private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance, int duration, int overFare) { + private Long 지하철_노선_생성_요청(String name, String color, Long upStation, Long downStation, int distance, int duration, int additionalFare, LocalTime startTime, LocalTime endTime, int intervalTime) { Map lineCreateParams = new HashMap<>(); lineCreateParams.put("name", name); lineCreateParams.put("color", color); @@ -459,7 +531,14 @@ void findPathWith19AgeByDistance() { lineCreateParams.put("downStationId", downStation + ""); lineCreateParams.put("distance", distance + ""); lineCreateParams.put("duration", duration + ""); - lineCreateParams.put("additionalFare", overFare + ""); + if (startTime != null) { + lineCreateParams.put("startTime", startTime.format(ISO_TIME)); + } + if (endTime != null) { + lineCreateParams.put("endTime", endTime.format(ISO_TIME)); + } + lineCreateParams.put("intervalTime", intervalTime + ""); + lineCreateParams.put("additionalFare", additionalFare + ""); return LineSteps.지하철_노선_생성_요청(관리자, lineCreateParams).jsonPath().getLong("id"); } diff --git a/src/test/java/nextstep/subway/acceptance/path/PathSteps.java b/src/test/java/nextstep/subway/acceptance/path/PathSteps.java index b61aa3170..73107d2b1 100644 --- a/src/test/java/nextstep/subway/acceptance/path/PathSteps.java +++ b/src/test/java/nextstep/subway/acceptance/path/PathSteps.java @@ -7,6 +7,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import java.time.LocalDateTime; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -37,6 +38,19 @@ public class PathSteps { .then().log().all().extract(); } + public static ExtractableResponse 두_역의_가장_빠른_도착_경로_조회를_요청(RequestSpecification given, Long source, Long target, String time) { + return given + .accept(MediaType.APPLICATION_JSON_VALUE) + .queryParams(Map.of( + "source", source, + "target", target, + "type", PathType.ARRIVAL_TIME.name(), + "time", time + )) + .when().get("/paths") + .then().log().all().extract(); + } + public static void 경로_조회_응답_검증(ExtractableResponse response, int distance, int duration, int fare, Long... stations) { assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); assertThat(response.jsonPath().getInt("distance")).isEqualTo(distance); @@ -44,4 +58,13 @@ public class PathSteps { assertThat(response.jsonPath().getInt("fare")).isEqualTo(fare); assertThat(response.jsonPath().getList("stations.id", Long.class)).containsExactly(stations); } + + public static void 경로_조회_응답_검증(ExtractableResponse response, int distance, int duration, int fare, String arrivalTime, Long... stations) { + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(response.jsonPath().getInt("distance")).isEqualTo(distance); + assertThat(response.jsonPath().getInt("duration")).isEqualTo(duration); + assertThat(response.jsonPath().getInt("fare")).isEqualTo(fare); + assertThat(response.jsonPath().getInt("arrivalTime")).isEqualTo(arrivalTime); + assertThat(response.jsonPath().getList("stations.id", Long.class)).containsExactly(stations); + } } From a202fd4b0b16a9d793fa186eda003dfaae302e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 15 Aug 2022 21:23:50 +0900 Subject: [PATCH 04/14] =?UTF-8?q?test=20=EB=AA=A8=EB=93=A0=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=A1=B0=ED=9A=8C=20study?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/nextstep/study/JgraphtTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/test/java/nextstep/study/JgraphtTest.java b/src/test/java/nextstep/study/JgraphtTest.java index ce58ec1a1..dc96d5958 100644 --- a/src/test/java/nextstep/study/JgraphtTest.java +++ b/src/test/java/nextstep/study/JgraphtTest.java @@ -4,6 +4,7 @@ import org.jgrapht.alg.shortestpath.DijkstraShortestPath; import org.jgrapht.alg.shortestpath.KShortestPaths; import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.graph.Multigraph; import org.jgrapht.graph.WeightedMultigraph; import org.junit.jupiter.api.Test; @@ -54,4 +55,27 @@ void getKShortestPaths() { assertThat(it.getVertexList()).endsWith(target); }); } + + @Test + public void getKShortestPathsWithMultigraph() { + String source = "v3"; + String target = "v1"; + + Multigraph graph = new Multigraph(DefaultWeightedEdge.class); + graph.addVertex("v1"); + graph.addVertex("v2"); + graph.addVertex("v3"); + + graph.addEdge("v1", "v2"); + graph.addEdge("v2", "v3"); + graph.addEdge("v1", "v3"); + + List paths = new KShortestPaths(graph, 1000).getPaths(source, target); + + assertThat(paths).hasSize(2); + paths.forEach(it -> { + assertThat(it.getVertexList()).startsWith(source); + assertThat(it.getVertexList()).endsWith(target); + }); + } } \ No newline at end of file From 53babb3fdd54934cbe0fd31575a6dcc7c4d28e56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 15 Aug 2022 21:30:43 +0900 Subject: [PATCH 05/14] =?UTF-8?q?fix=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EB=85=B8=EC=84=A0=20=EC=8B=9C=EA=B0=84=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/subway/applicaion/dto/LineRequest.java | 12 +++++++++--- .../subway/acceptance/line/LineAcceptanceTest.java | 2 ++ .../nextstep/subway/acceptance/line/LineSteps.java | 9 +++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java b/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java index 6df2ec542..2837efe78 100644 --- a/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java +++ b/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java @@ -4,6 +4,8 @@ import java.time.LocalTime; +import static java.time.format.DateTimeFormatter.ISO_TIME; + public class LineRequest { private String name; private String color; @@ -19,11 +21,15 @@ public class LineRequest { public LineRequest() { } - public LineRequest(String name, String color, LocalTime startTime, LocalTime endTime, int intervalTime, int additionalFare, Long upStationId, Long downStationId, int distance, int duration) { + public LineRequest(String name, String color, String startTime, String endTime, int intervalTime, int additionalFare, Long upStationId, Long downStationId, int distance, int duration) { this.name = name; this.color = color; - this.startTime = startTime; - this.endTime = endTime; + if (startTime != null) { + this.startTime = LocalTime.parse(startTime, ISO_TIME); + } + if (endTime != null) { + this.endTime = LocalTime.parse(endTime, ISO_TIME); + } this.intervalTime = intervalTime; this.additionalFare = additionalFare; this.upStationId = upStationId; diff --git a/src/test/java/nextstep/subway/acceptance/line/LineAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/line/LineAcceptanceTest.java index d53319f2a..0b373ecd5 100644 --- a/src/test/java/nextstep/subway/acceptance/line/LineAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/line/LineAcceptanceTest.java @@ -8,7 +8,9 @@ import org.springframework.http.HttpStatus; import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import static java.time.format.DateTimeFormatter.ISO_TIME; import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_검증; import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_목록_조회_요청; import static nextstep.subway.acceptance.line.LineSteps.지하철_노선_삭제_요청; diff --git a/src/test/java/nextstep/subway/acceptance/line/LineSteps.java b/src/test/java/nextstep/subway/acceptance/line/LineSteps.java index 63d85a0a8..1c636ed25 100644 --- a/src/test/java/nextstep/subway/acceptance/line/LineSteps.java +++ b/src/test/java/nextstep/subway/acceptance/line/LineSteps.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; +import static java.time.format.DateTimeFormatter.ISO_TIME; import static org.assertj.core.api.Assertions.assertThat; public class LineSteps extends AcceptanceTestSteps { @@ -24,12 +25,12 @@ public class LineSteps extends AcceptanceTestSteps { } public static ExtractableResponse 지하철_노선_생성_요청(String token, String name, String color, LocalTime startTime, LocalTime endTime, int intervalTime) { - Map params = new HashMap<>(); + Map params = new HashMap<>(); params.put("name", name); params.put("color", color); - params.put("startTime", startTime); - params.put("endTime", endTime); - params.put("intervalTime", intervalTime); + params.put("startTime", startTime.format(ISO_TIME)); + params.put("endTime", endTime.format(ISO_TIME)); + params.put("intervalTime", intervalTime + ""); return given(token) .body(params) .contentType(MediaType.APPLICATION_JSON_VALUE) From e247492168b027fd9e872eb3af3472e627ea5f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Wed, 17 Aug 2022 15:03:35 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat=20=EC=A7=80=ED=95=98=EC=B2=A0=20?= =?UTF-8?q?=EA=B0=80=EC=9E=A5=20=EB=B9=A0=EB=A5=B8=20=EB=8F=84=EC=B0=A9=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/applicaion/PathService.java | 12 +-- .../subway/applicaion/dto/PathResponse.java | 17 ++- .../nextstep/subway/domain/ArrivalTime.java | 100 ++++++++++++++++++ ...iscountPolicy.java => DiscountPolicy.java} | 4 +- .../java/nextstep/subway/domain/Fare.java | 12 ++- .../nextstep/subway/domain/FarePolicy.java | 16 +-- .../java/nextstep/subway/domain/Line.java | 4 + .../java/nextstep/subway/domain/Path.java | 21 +++- .../java/nextstep/subway/domain/PathType.java | 21 ++-- .../java/nextstep/subway/domain/Sections.java | 4 + .../SubwayMap/ArrivalTimeSubwayMap.java | 77 ++++++++++++++ .../domain/SubwayMap/DistanceSubwayMap.java | 62 +++++++++++ .../DurationSubwayMap.java} | 53 +++------- .../subway/domain/SubwayMap/SubwayMap.java | 53 ++++++++++ .../nextstep/subway/ui/PathController.java | 4 +- src/test/java/nextstep/study/JgraphtTest.java | 24 +---- .../acceptance/path/PathAcceptanceTest.java | 57 +++++----- .../subway/acceptance/path/PathSteps.java | 8 +- .../documentation/PathDocumentation.java | 11 +- ...olicyTest.java => DiscountPolicyTest.java} | 24 ++--- .../nextstep/subway/unit/FarePolicyTest.java | 16 +-- .../java/nextstep/subway/unit/PathTest.java | 10 +- .../nextstep/subway/unit/SubwayMapTest.java | 18 ++-- 23 files changed, 464 insertions(+), 164 deletions(-) create mode 100644 src/main/java/nextstep/subway/domain/ArrivalTime.java rename src/main/java/nextstep/subway/domain/{AgeDiscountPolicy.java => DiscountPolicy.java} (92%) create mode 100644 src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java create mode 100644 src/main/java/nextstep/subway/domain/SubwayMap/DistanceSubwayMap.java rename src/main/java/nextstep/subway/domain/{SubwayMap.java => SubwayMap/DurationSubwayMap.java} (52%) create mode 100644 src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java rename src/test/java/nextstep/subway/unit/{AgeDiscountPolicyTest.java => DiscountPolicyTest.java} (64%) diff --git a/src/main/java/nextstep/subway/applicaion/PathService.java b/src/main/java/nextstep/subway/applicaion/PathService.java index d0841bd96..d838dea74 100644 --- a/src/main/java/nextstep/subway/applicaion/PathService.java +++ b/src/main/java/nextstep/subway/applicaion/PathService.java @@ -1,13 +1,13 @@ package nextstep.subway.applicaion; import nextstep.member.application.MemberService; -import nextstep.subway.domain.AgeDiscountPolicy; +import nextstep.subway.domain.DiscountPolicy; import nextstep.subway.domain.PathType; import nextstep.subway.applicaion.dto.PathResponse; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; import nextstep.subway.domain.Station; -import nextstep.subway.domain.SubwayMap; +import nextstep.subway.domain.SubwayMap.SubwayMap; import org.springframework.stereotype.Service; import support.auth.userdetails.User; @@ -25,13 +25,13 @@ public PathService(LineService lineService, StationService stationService, Membe this.memberService = memberService; } - public PathResponse findPath(User user, Long source, Long target, PathType type) { + public PathResponse findPath(User user, Long source, Long target, PathType type, String time) { Station upStation = stationService.findById(source); Station downStation = stationService.findById(target); List lines = lineService.findLines(); - AgeDiscountPolicy ageDiscountPolicy = AgeDiscountPolicy.of(getCurrentUserAge(user.getUsername())); - SubwayMap subwayMap = new SubwayMap(lines); - Path path = subwayMap.findPath(upStation, downStation, type, ageDiscountPolicy); + DiscountPolicy discountPolicy = DiscountPolicy.of(getCurrentUserAge(user.getUsername())); + SubwayMap subwayMap = type.getInstance(lines); + Path path = subwayMap.findPath(upStation, downStation, discountPolicy, time); return PathResponse.of(path); } diff --git a/src/main/java/nextstep/subway/applicaion/dto/PathResponse.java b/src/main/java/nextstep/subway/applicaion/dto/PathResponse.java index 9c7c78145..df968a23e 100644 --- a/src/main/java/nextstep/subway/applicaion/dto/PathResponse.java +++ b/src/main/java/nextstep/subway/applicaion/dto/PathResponse.java @@ -2,20 +2,25 @@ import nextstep.subway.domain.Path; +import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; +import static nextstep.support.entity.Formatters.DATE_TIME_PATH; + public class PathResponse { private List stations; private int distance; private int duration; private int fare; + private LocalDateTime arrivalTime; - public PathResponse(List stations, int distance, int duration, int fare) { + public PathResponse(List stations, int distance, int duration, int fare, LocalDateTime arrivalTime) { this.stations = stations; this.distance = distance; this.duration = duration; this.fare = fare; + this.arrivalTime = arrivalTime; } public static PathResponse of(Path path) { @@ -25,7 +30,8 @@ public static PathResponse of(Path path) { int distance = path.extractDistance(); int duration = path.extractDuration(); int fare = path.getFare(); - return new PathResponse(stations, distance, duration, fare); + LocalDateTime arrivalTime = path.getArrivalTime(); + return new PathResponse(stations, distance, duration, fare, arrivalTime); } public List getStations() { @@ -43,4 +49,11 @@ public int getDuration() { public int getFare() { return fare; } + + public String getArrivalTime() { + if (this.arrivalTime == null) { + return ""; + } + return arrivalTime.format(DATE_TIME_PATH); + } } diff --git a/src/main/java/nextstep/subway/domain/ArrivalTime.java b/src/main/java/nextstep/subway/domain/ArrivalTime.java new file mode 100644 index 000000000..ccabcba2f --- /dev/null +++ b/src/main/java/nextstep/subway/domain/ArrivalTime.java @@ -0,0 +1,100 @@ +package nextstep.subway.domain; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.List; + +import static nextstep.support.entity.Formatters.DATE_TIME_PATH; + +public class ArrivalTime { + private List
sections; + private List lines; + private LocalDateTime dateTime; + private LocalTime startTime; + + public ArrivalTime(List
sections, List lines, String time) { + this.sections = sections; + this.lines = lines; + this.dateTime = LocalDateTime.parse(time, DATE_TIME_PATH); + this.startTime = findLine(sections.get(0)).getStartTime(); + } + + public LocalDateTime value() { + return getTimeOfFastestPath(dateTime); + } + + private LocalDateTime getTimeOfFastestPath(LocalDateTime dateTime) { + LocalDateTime currentDateTime = dateTime; + Line currentLine = null; + + for (Section section : sections) { + Line line = findLine(section); + if (!line.equals(currentLine)) { + currentLine = line; + currentDateTime = calculateTimeInBeforeStations(currentDateTime, currentLine, section); + + if (goingLastTime(currentDateTime, currentLine.getEndTime())) { + return getTimeOfFirstPath(); + } + } + + currentDateTime = currentDateTime.plusMinutes(section.getDuration()); + } + + return currentDateTime; + } + + private LocalDateTime getTimeOfFirstPath() { + LocalDateTime nextDayAtStartDateTime = changeTime(dateTime.plusDays(1), startTime); + return getTimeOfFastestPath(nextDayAtStartDateTime); + } + + private boolean goingLastTime(LocalDateTime dateTime, LocalTime endTime) { + return dateTime.isAfter(changeTime(dateTime, endTime)); + } + + private LocalDateTime calculateTimeInBeforeStations(LocalDateTime currentDateTime, Line currentLine, Section section) { + LocalDateTime targetTime = changeTime(currentDateTime, currentLine.getStartTime()); + LocalDateTime fastestDepartureTime = findFastestDepartureTime(currentDateTime, targetTime, currentLine.getIntervalTime()); + int beforeDuration = getBeforeDuration(currentLine, section); + return fastestDepartureTime.plusMinutes(beforeDuration); + } + + private Line findLine(Section section) { + return lines.stream() + .filter(item -> item.getSections().contains(section)) + .findFirst() + .get(); + } + + private LocalDateTime changeTime(LocalDateTime dateTime, LocalTime time) { + return LocalDateTime.of(dateTime.toLocalDate(), time); + } + + private int getBeforeDuration(Line line, Section currentSection) { + List
beforeSections = new ArrayList<>(); + for (Section section : line.getSections()) { + if (section.equals(currentSection)) { + break; + } + beforeSections.add(section); + } + + return new Sections(beforeSections).totalDuration(); + } + + private LocalDateTime findFastestDepartureTime(LocalDateTime referenceTime, LocalDateTime targetTime, int interval) { + if (referenceTime.isAfter(targetTime)) { + return findFastestDepartureTime(referenceTime, targetTime.plusMinutes(interval), interval); + } + return targetTime; + } + + private LocalTime findTimeRequired(LocalTime baseTime, LocalTime time, int interval) { + if (baseTime.isAfter(time)) { + return findTimeRequired(baseTime, time.plusMinutes(interval), interval); + } + return time; + } +} diff --git a/src/main/java/nextstep/subway/domain/AgeDiscountPolicy.java b/src/main/java/nextstep/subway/domain/DiscountPolicy.java similarity index 92% rename from src/main/java/nextstep/subway/domain/AgeDiscountPolicy.java rename to src/main/java/nextstep/subway/domain/DiscountPolicy.java index dd2276d46..f9325e582 100644 --- a/src/main/java/nextstep/subway/domain/AgeDiscountPolicy.java +++ b/src/main/java/nextstep/subway/domain/DiscountPolicy.java @@ -1,6 +1,6 @@ package nextstep.subway.domain; -public enum AgeDiscountPolicy { +public enum DiscountPolicy { BABY { @Override public int discount(int fare) { @@ -28,7 +28,7 @@ public int discount(int fare) { public static final String INVALID_AGE_MESSAGE = "It starts from 1 year old in Korean age."; - public static AgeDiscountPolicy of(int age) { + public static DiscountPolicy of(int age) { if (age <= 0) { throw new IllegalArgumentException(INVALID_AGE_MESSAGE); } diff --git a/src/main/java/nextstep/subway/domain/Fare.java b/src/main/java/nextstep/subway/domain/Fare.java index fe9e15fa8..eb48c81cf 100644 --- a/src/main/java/nextstep/subway/domain/Fare.java +++ b/src/main/java/nextstep/subway/domain/Fare.java @@ -3,19 +3,21 @@ public class Fare { private int totalDistance; private int additionalFare; - private AgeDiscountPolicy ageDiscountPolicy; + private DiscountPolicy discountPolicy; + private FarePolicy farePolicy; - public Fare(int totalDistance, int additionalFare, AgeDiscountPolicy ageDiscountPolicy) { + public Fare(int totalDistance, int additionalFare, DiscountPolicy discountPolicy) { this.totalDistance = totalDistance; this.additionalFare = additionalFare; - this.ageDiscountPolicy = ageDiscountPolicy; + this.discountPolicy = discountPolicy; + this.farePolicy = FarePolicy.of(totalDistance); } public Fare(int totalDistance) { - this(totalDistance, 0, AgeDiscountPolicy.ADULT); + this(totalDistance, 0, DiscountPolicy.ADULT); } public int value() { - return ageDiscountPolicy.discount(additionalFare + FarePolicy.calculateFare(totalDistance)); + return discountPolicy.discount(additionalFare + farePolicy.calculate(totalDistance)); } } diff --git a/src/main/java/nextstep/subway/domain/FarePolicy.java b/src/main/java/nextstep/subway/domain/FarePolicy.java index 951b3c78b..97fdfdb94 100644 --- a/src/main/java/nextstep/subway/domain/FarePolicy.java +++ b/src/main/java/nextstep/subway/domain/FarePolicy.java @@ -3,19 +3,19 @@ public enum FarePolicy { DEFAULT { @Override - protected int calculate(int distance) { + public int calculate(int distance) { return DEFAULT_FARE; } }, TEN_KILO { @Override - protected int calculate(int distance) { + public int calculate(int distance) { return DEFAULT_FARE + calculateOverFare(distance - TEN_KILO_METER, EVERY_5KM); } }, FIFTY_KILO { @Override - protected int calculate(int distance) { + public int calculate(int distance) { return DEFAULT_FARE + calculateOverFare(FIFTY_KILO_METER - TEN_KILO_METER, EVERY_5KM) + calculateOverFare(distance - FIFTY_KILO_METER, EVERY_8KM); } @@ -27,20 +27,20 @@ protected int calculate(int distance) { public static final int EVERY_5KM = 5; public static final int EVERY_8KM = 8; - public static int calculateFare(int distance) { + public static FarePolicy of(int distance) { if (distance < TEN_KILO_METER) { - return DEFAULT.calculate(distance); + return DEFAULT; } if (distance < FIFTY_KILO_METER) { - return TEN_KILO.calculate(distance); + return TEN_KILO; } - return FIFTY_KILO.calculate(distance); + return FIFTY_KILO; } protected int calculateOverFare(int distance, int overKiloMeter) { return (int) ((Math.ceil((distance - 1) / overKiloMeter) + 1) * 100); } - protected abstract int calculate(int distance); + public abstract int calculate(int distance); } diff --git a/src/main/java/nextstep/subway/domain/Line.java b/src/main/java/nextstep/subway/domain/Line.java index e15f9b07c..e6a80ceb7 100644 --- a/src/main/java/nextstep/subway/domain/Line.java +++ b/src/main/java/nextstep/subway/domain/Line.java @@ -117,6 +117,10 @@ public void deleteSection(Station station) { sections.delete(station); } + public boolean matchSection(Section section) { + return this.sections.contains(section); + } + public static class LineBuilder { private String name; private String color; diff --git a/src/main/java/nextstep/subway/domain/Path.java b/src/main/java/nextstep/subway/domain/Path.java index 3ea1b6d8c..8f7b4af8b 100644 --- a/src/main/java/nextstep/subway/domain/Path.java +++ b/src/main/java/nextstep/subway/domain/Path.java @@ -1,22 +1,33 @@ package nextstep.subway.domain; +import java.time.LocalDateTime; import java.util.List; public class Path { private Sections sections; private Fare fare; + private ArrivalTime arrivalTime; public Path(Sections sections, Fare fare) { + this(sections, fare, null); + } + + public Path(Path path, ArrivalTime arrivalTime) { + this(path.sections, path.fare, arrivalTime); + } + + public Path(Sections sections, Fare fare, ArrivalTime arrivalTime) { this.sections = sections; this.fare = fare; + this.arrivalTime = arrivalTime; } public Path(Sections sections) { this(sections, new Fare(sections.totalDistance())); } - public Sections getSections() { - return sections; + public List
getSections() { + return sections.getSections(); } public int extractDistance() { @@ -35,4 +46,10 @@ public List getStations() { return sections.getStations(); } + public LocalDateTime getArrivalTime() { + if (arrivalTime == null) { + return null; + } + return arrivalTime.value(); + } } diff --git a/src/main/java/nextstep/subway/domain/PathType.java b/src/main/java/nextstep/subway/domain/PathType.java index 6a87c9745..19a4ca7d7 100644 --- a/src/main/java/nextstep/subway/domain/PathType.java +++ b/src/main/java/nextstep/subway/domain/PathType.java @@ -1,24 +1,31 @@ package nextstep.subway.domain; +import nextstep.subway.domain.SubwayMap.ArrivalTimeSubwayMap; +import nextstep.subway.domain.SubwayMap.DistanceSubwayMap; +import nextstep.subway.domain.SubwayMap.DurationSubwayMap; +import nextstep.subway.domain.SubwayMap.SubwayMap; + +import java.util.List; + public enum PathType { DISTANCE { @Override - public int getEdgeWeight(Section section) { - return section.getDistance(); + public SubwayMap getInstance(List lines) { + return new DistanceSubwayMap(lines); } }, DURATION { @Override - public int getEdgeWeight(Section section) { - return section.getDuration(); + public SubwayMap getInstance(List lines) { + return new DurationSubwayMap(lines); } }, ARRIVAL_TIME { @Override - public int getEdgeWeight(Section section) { - return 0; + public SubwayMap getInstance(List lines) { + return new ArrivalTimeSubwayMap(lines); } }; - public abstract int getEdgeWeight(Section section); + public abstract SubwayMap getInstance(List lines); } diff --git a/src/main/java/nextstep/subway/domain/Sections.java b/src/main/java/nextstep/subway/domain/Sections.java index 5ddd64b1f..eb2096c4b 100644 --- a/src/main/java/nextstep/subway/domain/Sections.java +++ b/src/main/java/nextstep/subway/domain/Sections.java @@ -150,4 +150,8 @@ public int totalDistance() { public int totalDuration() { return sections.stream().mapToInt(Section::getDuration).sum(); } + + public boolean contains(Section section) { + return this.sections.contains(section); + } } diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java new file mode 100644 index 000000000..4c54fe55c --- /dev/null +++ b/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java @@ -0,0 +1,77 @@ +package nextstep.subway.domain.SubwayMap; + +import nextstep.subway.domain.ArrivalTime; +import nextstep.subway.domain.DiscountPolicy; +import nextstep.subway.domain.Line; +import nextstep.subway.domain.Path; +import nextstep.subway.domain.Section; +import nextstep.subway.domain.SectionEdge; +import nextstep.subway.domain.Station; +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.KShortestPaths; +import org.jgrapht.graph.Multigraph; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class ArrivalTimeSubwayMap extends SubwayMap { + + public ArrivalTimeSubwayMap(List lines) { + super(lines); + } + + @Override + public Path findPath(Station source, Station target, DiscountPolicy discountPolicy, String time) { + Path path = super.findPath(source, target, discountPolicy, time); + ArrivalTime arrivalTime = new ArrivalTime(path.getSections(), lines, time); + return new Path(path, arrivalTime); + } + + @Override + protected GraphPath getGraphPath(Station source, Station target) { + KShortestPaths kShortestPaths = new KShortestPaths(getGraph(), 100); + List paths = kShortestPaths.getPaths(source, target); + return paths.stream() + .min(Comparator.comparing(path -> path.getWeight())) + .orElse(null); + } + + private Multigraph getGraph() { + Multigraph graph = new Multigraph<>(SectionEdge.class); + + // 지하철 역(정점)을 등록 + lines.stream() + .flatMap(it -> it.getStations().stream()) + .distinct() + .collect(Collectors.toList()) + .forEach(it -> graph.addVertex(it)); + + // 지하철 역의 연결 정보(간선)을 등록 + lines.stream() + .flatMap(it -> it.getSections().stream()) + .forEach(it -> { + SectionEdge sectionEdge = SectionEdge.of(it); + graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); + graph.setEdgeWeight(sectionEdge, it.getDuration()); + }); + + // 지하철 역의 연결 정보(간선)을 등록 + lines.stream() + .flatMap(it -> it.getSections().stream()) + .map(it -> new Section( + it.getLine(), + it.getDownStation(), + it.getUpStation(), + it.getDistance(), + it.getDuration() + ) + ) + .forEach(it -> { + SectionEdge sectionEdge = SectionEdge.of(it); + graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); + graph.setEdgeWeight(sectionEdge, it.getDuration()); + }); + return graph; + } +} diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/DistanceSubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/DistanceSubwayMap.java new file mode 100644 index 000000000..91f63bd98 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/SubwayMap/DistanceSubwayMap.java @@ -0,0 +1,62 @@ +package nextstep.subway.domain.SubwayMap; + +import nextstep.subway.domain.Line; +import nextstep.subway.domain.Section; +import nextstep.subway.domain.SectionEdge; +import nextstep.subway.domain.Station; +import org.jgrapht.GraphPath; +import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.SimpleDirectedWeightedGraph; + +import java.util.List; +import java.util.stream.Collectors; + +public class DistanceSubwayMap extends SubwayMap { + public DistanceSubwayMap(List lines) { + super(lines); + } + + @Override + protected GraphPath getGraphPath(Station source, Station target) { + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(getGraph()); + return dijkstraShortestPath.getPath(source, target); + } + + private SimpleDirectedWeightedGraph getGraph() { + SimpleDirectedWeightedGraph graph = new SimpleDirectedWeightedGraph<>(SectionEdge.class); + + // 지하철 역(정점)을 등록 + lines.stream() + .flatMap(it -> it.getStations().stream()) + .distinct() + .collect(Collectors.toList()) + .forEach(it -> graph.addVertex(it)); + + // 지하철 역의 연결 정보(간선)을 등록 + lines.stream() + .flatMap(it -> it.getSections().stream()) + .forEach(it -> { + SectionEdge sectionEdge = SectionEdge.of(it); + graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); + graph.setEdgeWeight(sectionEdge, it.getDistance()); + }); + + // 지하철 역의 연결 정보(간선)을 등록 + lines.stream() + .flatMap(it -> it.getSections().stream()) + .map(it -> new Section( + it.getLine(), + it.getDownStation(), + it.getUpStation(), + it.getDistance(), + it.getDuration() + ) + ) + .forEach(it -> { + SectionEdge sectionEdge = SectionEdge.of(it); + graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); + graph.setEdgeWeight(sectionEdge, it.getDistance()); + }); + return graph; + } +} diff --git a/src/main/java/nextstep/subway/domain/SubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/DurationSubwayMap.java similarity index 52% rename from src/main/java/nextstep/subway/domain/SubwayMap.java rename to src/main/java/nextstep/subway/domain/SubwayMap/DurationSubwayMap.java index 549e6c307..49f797855 100644 --- a/src/main/java/nextstep/subway/domain/SubwayMap.java +++ b/src/main/java/nextstep/subway/domain/SubwayMap/DurationSubwayMap.java @@ -1,54 +1,29 @@ -package nextstep.subway.domain; +package nextstep.subway.domain.SubwayMap; -import nextstep.subway.exception.NotConnectSectionException; +import nextstep.subway.domain.Line; +import nextstep.subway.domain.Section; +import nextstep.subway.domain.SectionEdge; +import nextstep.subway.domain.Station; import org.jgrapht.GraphPath; import org.jgrapht.alg.shortestpath.DijkstraShortestPath; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.Stream; -public class SubwayMap { - private List lines; +public class DurationSubwayMap extends SubwayMap { - public SubwayMap(List lines) { - this.lines = lines; + public DurationSubwayMap(List lines) { + super(lines); } - public Path findPath(Station source, Station target, PathType type, AgeDiscountPolicy ageDiscountPolicy) { - // 다익스트라 최단 경로 찾기 - GraphPath result = getGraphPath(source, target, type); - - validateConnectSection(result); - - Sections sections = new Sections(streamSections(result).collect(Collectors.toList())); - - int maxAdditionalFare = streamSections(result) - .mapToInt(section -> section.getLine().getAdditionalFare()) - .max() - .orElse(0); - - return new Path(sections, new Fare(sections.totalDistance(), maxAdditionalFare, ageDiscountPolicy)); - } - - private Stream
streamSections(GraphPath result) { - return result.getEdgeList().stream() - .map(SectionEdge::getSection); - } - - private void validateConnectSection(GraphPath result) { - if (result == null) { - throw new NotConnectSectionException(); - } - } - - private GraphPath getGraphPath(Station source, Station target, PathType type) { - DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(getGraph(type)); + @Override + protected GraphPath getGraphPath(Station source, Station target) { + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(getGraph()); return dijkstraShortestPath.getPath(source, target); } - private SimpleDirectedWeightedGraph getGraph(PathType type) { + private SimpleDirectedWeightedGraph getGraph() { SimpleDirectedWeightedGraph graph = new SimpleDirectedWeightedGraph<>(SectionEdge.class); // 지하철 역(정점)을 등록 @@ -64,7 +39,7 @@ private SimpleDirectedWeightedGraph getGraph(PathType type .forEach(it -> { SectionEdge sectionEdge = SectionEdge.of(it); graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); - graph.setEdgeWeight(sectionEdge, type.getEdgeWeight(it)); + graph.setEdgeWeight(sectionEdge, it.getDuration()); }); // 지하철 역의 연결 정보(간선)을 등록 @@ -81,7 +56,7 @@ private SimpleDirectedWeightedGraph getGraph(PathType type .forEach(it -> { SectionEdge sectionEdge = SectionEdge.of(it); graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); - graph.setEdgeWeight(sectionEdge, type.getEdgeWeight(it)); + graph.setEdgeWeight(sectionEdge, it.getDuration()); }); return graph; } diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java new file mode 100644 index 000000000..569916a5b --- /dev/null +++ b/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java @@ -0,0 +1,53 @@ +package nextstep.subway.domain.SubwayMap; + +import nextstep.subway.domain.DiscountPolicy; +import nextstep.subway.domain.Fare; +import nextstep.subway.domain.Line; +import nextstep.subway.domain.Path; +import nextstep.subway.domain.Section; +import nextstep.subway.domain.SectionEdge; +import nextstep.subway.domain.Sections; +import nextstep.subway.domain.Station; +import nextstep.subway.exception.NotConnectSectionException; +import org.jgrapht.GraphPath; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public abstract class SubwayMap { + protected List lines; + + public SubwayMap(List lines) { + this.lines = lines; + } + + public Path findPath(Station source, Station target, DiscountPolicy discountPolicy, String time) { + // 다익스트라 최단 경로 찾기 + GraphPath result = getGraphPath(source, target); + + validateConnectSection(result); + + Sections sections = new Sections(streamSections(result).collect(Collectors.toList())); + + int maxAdditionalFare = streamSections(result) + .mapToInt(section -> section.getLine().getAdditionalFare()) + .max() + .orElse(0); + + return new Path(sections, new Fare(sections.totalDistance(), maxAdditionalFare, discountPolicy)); + } + + protected void validateConnectSection(GraphPath result) { + if (result == null) { + throw new NotConnectSectionException(); + } + } + + protected Stream
streamSections(GraphPath result) { + return result.getEdgeList().stream() + .map(SectionEdge::getSection); + } + + protected abstract GraphPath getGraphPath(Station source, Station target); +} diff --git a/src/main/java/nextstep/subway/ui/PathController.java b/src/main/java/nextstep/subway/ui/PathController.java index 765d17e08..7f592e6c1 100644 --- a/src/main/java/nextstep/subway/ui/PathController.java +++ b/src/main/java/nextstep/subway/ui/PathController.java @@ -19,7 +19,7 @@ public PathController(PathService pathService) { } @GetMapping("/paths") - public ResponseEntity findPath(@AuthenticationPrincipal User user, @RequestParam Long source, @RequestParam Long target, @RequestParam PathType type) { - return ResponseEntity.ok(pathService.findPath(user, source, target, type)); + public ResponseEntity findPath(@AuthenticationPrincipal User user, @RequestParam Long source, @RequestParam Long target, @RequestParam PathType type, @RequestParam String time) { + return ResponseEntity.ok(pathService.findPath(user, source, target, type, time)); } } diff --git a/src/test/java/nextstep/study/JgraphtTest.java b/src/test/java/nextstep/study/JgraphtTest.java index dc96d5958..8a8a93a92 100644 --- a/src/test/java/nextstep/study/JgraphtTest.java +++ b/src/test/java/nextstep/study/JgraphtTest.java @@ -51,31 +51,9 @@ void getKShortestPaths() { assertThat(paths).hasSize(2); paths.stream() .forEach(it -> { + System.out.println(it.getVertexList()); assertThat(it.getVertexList()).startsWith(source); assertThat(it.getVertexList()).endsWith(target); }); } - - @Test - public void getKShortestPathsWithMultigraph() { - String source = "v3"; - String target = "v1"; - - Multigraph graph = new Multigraph(DefaultWeightedEdge.class); - graph.addVertex("v1"); - graph.addVertex("v2"); - graph.addVertex("v3"); - - graph.addEdge("v1", "v2"); - graph.addEdge("v2", "v3"); - graph.addEdge("v1", "v3"); - - List paths = new KShortestPaths(graph, 1000).getPaths(source, target); - - assertThat(paths).hasSize(2); - paths.forEach(it -> { - assertThat(it.getVertexList()).startsWith(source); - assertThat(it.getVertexList()).endsWith(target); - }); - } } \ No newline at end of file diff --git a/src/test/java/nextstep/subway/acceptance/path/PathAcceptanceTest.java b/src/test/java/nextstep/subway/acceptance/path/PathAcceptanceTest.java index 1ede16ba2..67d112946 100644 --- a/src/test/java/nextstep/subway/acceptance/path/PathAcceptanceTest.java +++ b/src/test/java/nextstep/subway/acceptance/path/PathAcceptanceTest.java @@ -449,45 +449,31 @@ void findPathWith19AgeByDistance() { @DisplayName("가장 빠른 도착 경로 기준으로") @Nested class Context_with_Arrival_Time { - Long 서초역; - Long 강남역; - Long 역삼역; - Long 선릉역; - Long 양재역; - - Long 이호선; - Long 신분당선; @BeforeEach void setUp() { - 서초역 = 지하철역_생성_요청(관리자, "서초역").jsonPath().getLong("id"); - 강남역 = 지하철역_생성_요청(관리자, "강남역").jsonPath().getLong("id"); - 역삼역 = 지하철역_생성_요청(관리자, "역삼역").jsonPath().getLong("id"); - 선릉역 = 지하철역_생성_요청(관리자, "선릉역").jsonPath().getLong("id"); - 양재역 = 지하철역_생성_요청(관리자, "양재역").jsonPath().getLong("id"); - - 이호선 = 지하철_노선_생성_요청("2호선", "green", 서초역, 강남역, 5, 3, LocalTime.of(5, 0), LocalTime.of(23, 0), 10); - 지하철_노선에_지하철_구간_생성_요청(관리자, 이호선, createSectionCreateParams(역삼역, 선릉역, 7, 4)); - 지하철_노선에_지하철_구간_생성_요청(관리자, 이호선, createSectionCreateParams(역삼역, 선릉역, 4, 3)); + Long 노선E = 지하철_노선_생성_요청("노선E", "green", 지하철A역, 지하철B역, 5, 3, LocalTime.of(5, 0), LocalTime.of(23, 0), 10); + 지하철_노선에_지하철_구간_생성_요청(관리자, 노선E, createSectionCreateParams(지하철B역, 지하철C역, 7, 4)); + 지하철_노선에_지하철_구간_생성_요청(관리자, 노선E, createSectionCreateParams(지하철C역, 지하철D역, 4, 3)); - 신분당선 = 지하철_노선_생성_요청("신분당선", "red", 강남역, 양재역, 6, 4, 900, LocalTime.of(5, 0), LocalTime.of(23, 0), 20); + 지하철_노선_생성_요청("노선F", "red", 지하철B역, 지하철E역, 6, 4, 900, LocalTime.of(5, 0), LocalTime.of(23, 0), 20); } - @DisplayName("환승이 없는 서초역에서 선릉역까지 10:00 기준으로 조회하면, 도착 시간은 10:10 이다.") + @DisplayName("환승이 없는 지하철B역에서 지하철D역까지 10:00 기준으로 조회하면, 도착 시간은 10:10 이다.") @Test void findPathByArrivalTimeOfSameLine() { // given LocalDateTime dateTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(10, 00, 00)); // when - ExtractableResponse response = 두_역의_가장_빠른_도착_경로_조회를_요청(given(관리자), 서초역, 선릉역, dateTime.format(DATE_TIME_PATH)); + ExtractableResponse response = 두_역의_가장_빠른_도착_경로_조회를_요청(given(관리자), 지하철B역, 지하철D역, dateTime.format(DATE_TIME_PATH)); // then - final int 거리_16km = 16; - final int 시간_10분 = 10; + final int 거리_11km = 11; + final int 시간_7분 = 7; final int 요금_1350원 = 1350; final String 도착_시간_오늘_10시_10분 = dateTime.plusMinutes(10).format(DATE_TIME_PATH); - 경로_조회_응답_검증(response, 거리_16km, 시간_10분, 요금_1350원, 도착_시간_오늘_10시_10분, 서초역, 강남역, 역삼역, 선릉역); + 경로_조회_응답_검증(response, 거리_11km, 시간_7분, 요금_1350원, 도착_시간_오늘_10시_10분, 지하철B역, 지하철C역, 지하철D역); } @DisplayName("추가요금이 있는 신분당선이 포함된 환승이 있는 서초역에서 양재역까지 10:00 기준으로 조회하면, 도착 시간은 10:24 이다.") @@ -497,14 +483,31 @@ void findPathByArrivalTimeForTransfer() { LocalDateTime dateTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(10, 00, 00)); // when - ExtractableResponse response = 두_역의_가장_빠른_도착_경로_조회를_요청(given(관리자), 서초역, 양재역, dateTime.format(DATE_TIME_PATH)); + ExtractableResponse response = 두_역의_가장_빠른_도착_경로_조회를_요청(given(관리자), 지하철A역, 지하철E역, dateTime.format(DATE_TIME_PATH)); // then final int 거리_11km = 11; - final int 시간_10분 = 10; - final int 요금_2150원 = 2150; + final int 시간_7분 = 7; + final int 요금_2250원 = 2250; final String 도착_시간_오늘_10시_24분 = dateTime.plusMinutes(24).format(DATE_TIME_PATH); - 경로_조회_응답_검증(response, 거리_11km, 시간_10분, 요금_2150원, 도착_시간_오늘_10시_24분, 서초역, 강남역, 양재역); + 경로_조회_응답_검증(response, 거리_11km, 시간_7분, 요금_2250원, 도착_시간_오늘_10시_24분, 지하철A역, 지하철B역, 지하철E역); + } + + @DisplayName("환승이 없는 지하철B역에서 지하철D역까지 23:00 막차 시간 기준으로 조회하면, 도착 시간은 다음날 05:10 이다.") + @Test + void findPathByArrivalTimeOfSameLineAfterEndTime() { + // given + LocalDateTime dateTime = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 00, 00)); + + // when + ExtractableResponse response = 두_역의_가장_빠른_도착_경로_조회를_요청(given(관리자), 지하철B역, 지하철D역, dateTime.format(DATE_TIME_PATH)); + + // then + final int 거리_11km = 11; + final int 시간_7분 = 7; + final int 요금_1350원 = 1350; + final String 도착_시간_내일_5시_10분 = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(5, 10)).format(DATE_TIME_PATH); + 경로_조회_응답_검증(response, 거리_11km, 시간_7분, 요금_1350원, 도착_시간_내일_5시_10분, 지하철B역, 지하철C역, 지하철D역); } } diff --git a/src/test/java/nextstep/subway/acceptance/path/PathSteps.java b/src/test/java/nextstep/subway/acceptance/path/PathSteps.java index 73107d2b1..d6b0e2ad3 100644 --- a/src/test/java/nextstep/subway/acceptance/path/PathSteps.java +++ b/src/test/java/nextstep/subway/acceptance/path/PathSteps.java @@ -20,7 +20,8 @@ public class PathSteps { .queryParams(Map.of( "source", source, "target", target, - "type", PathType.DISTANCE.name() + "type", PathType.DISTANCE.name(), + "time", "" )) .when().get("/paths") .then().log().all().extract(); @@ -32,7 +33,8 @@ public class PathSteps { .queryParams(Map.of( "source", source, "target", target, - "type", PathType.DURATION.name() + "type", PathType.DURATION.name(), + "time", "" )) .when().get("/paths") .then().log().all().extract(); @@ -64,7 +66,7 @@ public class PathSteps { assertThat(response.jsonPath().getInt("distance")).isEqualTo(distance); assertThat(response.jsonPath().getInt("duration")).isEqualTo(duration); assertThat(response.jsonPath().getInt("fare")).isEqualTo(fare); - assertThat(response.jsonPath().getInt("arrivalTime")).isEqualTo(arrivalTime); + assertThat(response.jsonPath().getString("arrivalTime")).isEqualTo(arrivalTime); assertThat(response.jsonPath().getList("stations.id", Long.class)).containsExactly(stations); } } diff --git a/src/test/java/nextstep/subway/documentation/PathDocumentation.java b/src/test/java/nextstep/subway/documentation/PathDocumentation.java index bc3587b69..e47b28fde 100644 --- a/src/test/java/nextstep/subway/documentation/PathDocumentation.java +++ b/src/test/java/nextstep/subway/documentation/PathDocumentation.java @@ -19,6 +19,7 @@ import static nextstep.subway.acceptance.path.PathSteps.두_역의_최단_거리_경로_조회를_요청; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; @@ -37,10 +38,10 @@ void path() { Lists.newArrayList( new StationResponse(1L, "강남역", now, now), new StationResponse(2L, "역삼역", now, now) - ), 10, 4, 1250 + ), 10, 4, 1250, null ); String accessToken = 로그인_되어_있음("member@email.com", "password"); - when(pathService.findPath(any(User.class), anyLong(), anyLong(), any(PathType.class))).thenReturn(pathResponse); + when(pathService.findPath(any(User.class), anyLong(), anyLong(), any(PathType.class), anyString())).thenReturn(pathResponse); 두_역의_최단_거리_경로_조회를_요청(given(accessToken, spec, "path", getRequestParametersSnippet(), getResponseFieldsSnippet()), 1L, 2L); } @@ -53,7 +54,8 @@ private ResponseFieldsSnippet getResponseFieldsSnippet() { fieldWithPath("stations[].modifiedDate").type(JsonFieldType.STRING).description("수정일자"), fieldWithPath("distance").type(JsonFieldType.NUMBER).description("거리"), fieldWithPath("duration").type(JsonFieldType.NUMBER).description("경과 시간"), - fieldWithPath("fare").type(JsonFieldType.NUMBER).description("요금") + fieldWithPath("fare").type(JsonFieldType.NUMBER).description("요금"), + fieldWithPath("arrivalTime").type(JsonFieldType.STRING).description("도착 시간") ); } @@ -61,7 +63,8 @@ private RequestParametersSnippet getRequestParametersSnippet() { return requestParameters( parameterWithName("source").description("출발역 ID"), parameterWithName("target").description("도착역 ID"), - parameterWithName("type").description("경로 조회 타입") + parameterWithName("type").description("경로 조회 타입"), + parameterWithName("time").description("조회 기준 시간") ); } } diff --git a/src/test/java/nextstep/subway/unit/AgeDiscountPolicyTest.java b/src/test/java/nextstep/subway/unit/DiscountPolicyTest.java similarity index 64% rename from src/test/java/nextstep/subway/unit/AgeDiscountPolicyTest.java rename to src/test/java/nextstep/subway/unit/DiscountPolicyTest.java index fb8c1ccd5..bdb8a43f6 100644 --- a/src/test/java/nextstep/subway/unit/AgeDiscountPolicyTest.java +++ b/src/test/java/nextstep/subway/unit/DiscountPolicyTest.java @@ -1,57 +1,57 @@ package nextstep.subway.unit; -import nextstep.subway.domain.AgeDiscountPolicy; +import nextstep.subway.domain.DiscountPolicy; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static nextstep.subway.domain.AgeDiscountPolicy.INVALID_AGE_MESSAGE; +import static nextstep.subway.domain.DiscountPolicy.INVALID_AGE_MESSAGE; import static nextstep.subway.domain.FarePolicy.DEFAULT_FARE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @DisplayName("나이별 요금 할인 정책 테스트") -class AgeDiscountPolicyTest { +class DiscountPolicyTest { @DisplayName("5세 이하 영유아는 무료") @Test void baby() { - AgeDiscountPolicy ageDiscountPolicy = AgeDiscountPolicy.of(5); - assertThat(ageDiscountPolicy.discount(DEFAULT_FARE)).isEqualTo(0); + DiscountPolicy discountPolicy = DiscountPolicy.of(5); + assertThat(discountPolicy.discount(DEFAULT_FARE)).isEqualTo(0); } @DisplayName("6세 이상 13세 미만 어린이는 운임에서 350원을 공제한 금액의 50%할인") @Test void children() { - AgeDiscountPolicy ageOf6 = AgeDiscountPolicy.of(6); + DiscountPolicy ageOf6 = DiscountPolicy.of(6); assertThat(ageOf6.discount(DEFAULT_FARE)).isEqualTo(450); - AgeDiscountPolicy ageOf12 = AgeDiscountPolicy.of(12); + DiscountPolicy ageOf12 = DiscountPolicy.of(12); assertThat(ageOf12.discount(DEFAULT_FARE)).isEqualTo(450); } @DisplayName("13세 이상 19세 미만 청소년은 운임에서 350원을 공제한 금액의 20%할인") @Test void teenager() { - AgeDiscountPolicy ageOf13 = AgeDiscountPolicy.of(13); + DiscountPolicy ageOf13 = DiscountPolicy.of(13); assertThat(ageOf13.discount(DEFAULT_FARE)).isEqualTo(720); - AgeDiscountPolicy ageOf18 = AgeDiscountPolicy.of(18); + DiscountPolicy ageOf18 = DiscountPolicy.of(18); assertThat(ageOf18.discount(DEFAULT_FARE)).isEqualTo(720); } @DisplayName("19세 이상 어른은 기본 운임비") @Test void adult() { - AgeDiscountPolicy ageOf19 = AgeDiscountPolicy.of(19); + DiscountPolicy ageOf19 = DiscountPolicy.of(19); assertThat(ageOf19.discount(DEFAULT_FARE)).isEqualTo(1250); } @DisplayName("0 또는 음수는 예외 발생") @Test void exception() { - assertThatThrownBy(() -> AgeDiscountPolicy.of(0), INVALID_AGE_MESSAGE) + assertThatThrownBy(() -> DiscountPolicy.of(0), INVALID_AGE_MESSAGE) .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> AgeDiscountPolicy.of(-1), INVALID_AGE_MESSAGE) + assertThatThrownBy(() -> DiscountPolicy.of(-1), INVALID_AGE_MESSAGE) .isInstanceOf(IllegalArgumentException.class); } } \ No newline at end of file diff --git a/src/test/java/nextstep/subway/unit/FarePolicyTest.java b/src/test/java/nextstep/subway/unit/FarePolicyTest.java index 782113492..979334d35 100644 --- a/src/test/java/nextstep/subway/unit/FarePolicyTest.java +++ b/src/test/java/nextstep/subway/unit/FarePolicyTest.java @@ -13,28 +13,28 @@ class FarePolicyTest { @DisplayName("10KM 요금 계산") @Test void defaultFare() { - int fare = FarePolicy.calculateFare(9); - assertThat(fare).isEqualTo(DEFAULT_FARE); + FarePolicy farePolicy = FarePolicy.of(9); + assertThat(farePolicy.calculate(9)).isEqualTo(DEFAULT_FARE); } @DisplayName("10km 초과 요금 조회") @Test void over10KmFare() { - int fare = FarePolicy.calculateFare(10); - assertThat(fare).isEqualTo(DEFAULT_FARE + 100); + FarePolicy farePolicy = FarePolicy.of(10); + assertThat(farePolicy.calculate(10)).isEqualTo(DEFAULT_FARE + 100); } @DisplayName("49km 요금 조회") @Test void over49KmFare() { - int fare = FarePolicy.calculateFare(49); - assertThat(fare).isEqualTo(DEFAULT_FARE + 800); + FarePolicy farePolicy = FarePolicy.of(49); + assertThat(farePolicy.calculate(49)).isEqualTo(DEFAULT_FARE + 800); } @DisplayName("50km 초과 요금 조회") @Test void over50KmFare() { - int fare = FarePolicy.calculateFare(50); - assertThat(fare).isEqualTo(DEFAULT_FARE + 800 + 100); + FarePolicy farePolicy = FarePolicy.of(50); + assertThat(farePolicy.calculate(50)).isEqualTo(DEFAULT_FARE + 800 + 100); } } \ No newline at end of file diff --git a/src/test/java/nextstep/subway/unit/PathTest.java b/src/test/java/nextstep/subway/unit/PathTest.java index 61ad18846..32d6b5c06 100644 --- a/src/test/java/nextstep/subway/unit/PathTest.java +++ b/src/test/java/nextstep/subway/unit/PathTest.java @@ -1,6 +1,6 @@ package nextstep.subway.unit; -import nextstep.subway.domain.AgeDiscountPolicy; +import nextstep.subway.domain.DiscountPolicy; import nextstep.subway.domain.Fare; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; @@ -76,7 +76,7 @@ void over50KmFare() { void defaultFareWith_신분당선() { Section AB구간 = new Section(신분당선, 지하철A역, 지하철B역, 9, 3); Sections sections = new Sections(List.of(AB구간)); - Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), AgeDiscountPolicy.ADULT)); + Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountPolicy.ADULT)); final int 요금_2150원 = DEFAULT_FARE + 신분당선.getAdditionalFare(); final int 거리_9km = 9; @@ -88,7 +88,7 @@ void over50KmFare() { void over10KmFareWith_신분당선() { Section AB구간 = new Section(신분당선, 지하철A역, 지하철B역, 10, 3); Sections sections = new Sections(List.of(AB구간)); - Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), AgeDiscountPolicy.ADULT)); + Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountPolicy.ADULT)); final int 요금_2250원 = DEFAULT_FARE + 신분당선.getAdditionalFare() + 100; final int 거리_10km = 10; @@ -100,7 +100,7 @@ void over50KmFare() { void over49KmFareWith_신분당선() { Section AB구간 = new Section(신분당선, 지하철A역, 지하철B역, 49, 3); Sections sections = new Sections(List.of(AB구간)); - Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), AgeDiscountPolicy.ADULT)); + Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountPolicy.ADULT)); final int 요금_2950원 = DEFAULT_FARE + 신분당선.getAdditionalFare() + 800; final int 거리_49km = 49; @@ -112,7 +112,7 @@ void over50KmFare() { void over50KmFareWith_신분당선() { Section AB구간 = new Section(신분당선, 지하철A역, 지하철B역, 50, 3); Sections sections = new Sections(List.of(AB구간)); - Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), AgeDiscountPolicy.ADULT)); + Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountPolicy.ADULT)); final int 요금_3050원 = DEFAULT_FARE + 신분당선.getAdditionalFare() + 800 + 100; final int 거리_50km = 50; diff --git a/src/test/java/nextstep/subway/unit/SubwayMapTest.java b/src/test/java/nextstep/subway/unit/SubwayMapTest.java index 4d589ff04..a78c6081b 100644 --- a/src/test/java/nextstep/subway/unit/SubwayMapTest.java +++ b/src/test/java/nextstep/subway/unit/SubwayMapTest.java @@ -1,11 +1,11 @@ package nextstep.subway.unit; -import nextstep.subway.domain.AgeDiscountPolicy; -import nextstep.subway.domain.PathType; +import nextstep.subway.domain.DiscountPolicy; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; +import nextstep.subway.domain.PathType; import nextstep.subway.domain.Station; -import nextstep.subway.domain.SubwayMap; +import nextstep.subway.domain.SubwayMap.SubwayMap; import org.assertj.core.util.Lists; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -48,10 +48,10 @@ void setUp() { void findPathWithDistance() { // given List lines = Lists.newArrayList(신분당선, 이호선, 삼호선); - SubwayMap subwayMap = new SubwayMap(lines); + SubwayMap subwayMap = PathType.DISTANCE.getInstance(lines); // when - Path path = subwayMap.findPath(교대역, 양재역, PathType.DISTANCE, AgeDiscountPolicy.ADULT); + Path path = subwayMap.findPath(교대역, 양재역, DiscountPolicy.ADULT, null); // then assertThat(path.getStations()).containsExactlyElementsOf(Lists.newArrayList(교대역, 강남역, 양재역)); @@ -62,10 +62,10 @@ void findPathWithDistance() { void findPathWithDuration() { // given List lines = Lists.newArrayList(신분당선, 이호선, 삼호선); - SubwayMap subwayMap = new SubwayMap(lines); + SubwayMap subwayMap = PathType.DURATION.getInstance(lines); // when - Path path = subwayMap.findPath(교대역, 양재역, PathType.DURATION, AgeDiscountPolicy.ADULT); + Path path = subwayMap.findPath(교대역, 양재역, DiscountPolicy.ADULT, null); // then assertThat(path.getStations()).containsExactlyElementsOf(Lists.newArrayList(교대역, 남부터미널역, 양재역)); @@ -76,10 +76,10 @@ void findPathWithDuration() { void findPathOppositely() { // given List lines = Lists.newArrayList(신분당선, 이호선, 삼호선); - SubwayMap subwayMap = new SubwayMap(lines); + SubwayMap subwayMap = PathType.DISTANCE.getInstance(lines); // when - Path path = subwayMap.findPath(양재역, 교대역, PathType.DISTANCE, AgeDiscountPolicy.ADULT); + Path path = subwayMap.findPath(양재역, 교대역, DiscountPolicy.ADULT, null); // then assertThat(path.getStations()).containsExactlyElementsOf(Lists.newArrayList(양재역, 강남역, 교대역)); From 00736ec865e8657b971a681c951d8e7a94a50244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Wed, 17 Aug 2022 15:40:43 +0900 Subject: [PATCH 07/14] =?UTF-8?q?test=20=EB=8F=84=EC=B0=A9=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EA=B4=80=EB=A0=A8=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/domain/ArrivalTimeTest.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/test/java/nextstep/subway/domain/ArrivalTimeTest.java diff --git a/src/test/java/nextstep/subway/domain/ArrivalTimeTest.java b/src/test/java/nextstep/subway/domain/ArrivalTimeTest.java new file mode 100644 index 000000000..9b5cea42f --- /dev/null +++ b/src/test/java/nextstep/subway/domain/ArrivalTimeTest.java @@ -0,0 +1,122 @@ +package nextstep.subway.domain; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +import static nextstep.support.entity.Formatters.DATE_TIME_PATH; +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("도착시간 관련 테스트") +class ArrivalTimeTest { + Station 지하철A = new Station("지하철A"); + Station 지하철B = new Station("지하철B"); + Station 지하철C = new Station("지하철C"); + Station 지하철D = new Station("지하철D"); + Station 지하철E = new Station("지하철E"); + + Line 노선A; + Line 노선B; + + Section 구간AB; + Section 구간BC; + Section 구간CD; + Section 구간BE; + + @BeforeEach + void setUp() { + 노선A = Line.builder() + .name("노선A") + .color("red") + .startTime(LocalTime.of(5, 0)) + .endTime(LocalTime.of(23, 0)) + .intervalTime(10) + .build(); + 노선A.addSection(지하철A, 지하철B, 5, 3); + 노선A.addSection(지하철B, 지하철C, 7, 4); + 노선A.addSection(지하철C, 지하철D, 4, 3); + + 노선B = Line.builder() + .name("노선B") + .color("orange") + .startTime(LocalTime.of(5, 0)) + .endTime(LocalTime.of(23, 0)) + .intervalTime(20) + .build(); + 노선B.addSection(지하철B, 지하철E, 6, 4); + + 구간AB = 노선A.getSections().get(0); + 구간BC = 노선A.getSections().get(1); + 구간CD = 노선A.getSections().get(2); + 구간BE = 노선B.getSections().get(0); + } + + @DisplayName("10:00 기준으로 환승이 없는 경로 도착시간 구하기") + @Test + void noneTransfer() { + // given + List lines = List.of(노선A, 노선B); + List
sections = List.of(구간BC, 구간CD); + String time = LocalDateTime.of(LocalDate.now(), LocalTime.of(10, 0)).format(DATE_TIME_PATH); + ArrivalTime arrivalTime = new ArrivalTime(sections, lines, time); + + // when + LocalDateTime value = arrivalTime.value(); + + // then + assertThat(value).isEqualTo(LocalDateTime.of(LocalDate.now(), LocalTime.of(10, 10))); + } + + @DisplayName("10:00 기준으로 환승이 있는 경로 도착시간 구하기") + @Test + void withTransfer() { + // given + List lines = List.of(노선A, 노선B); + List
sections = List.of(구간AB, 구간BE); + String time = LocalDateTime.of(LocalDate.now(), LocalTime.of(10, 0)).format(DATE_TIME_PATH); + ArrivalTime arrivalTime = new ArrivalTime(sections, lines, time); + + // when + LocalDateTime value = arrivalTime.value(); + + // then + assertThat(value).isEqualTo(LocalDateTime.of(LocalDate.now(), LocalTime.of(10, 24))); + } + + @DisplayName("막차 시간(23:00) 기준으로 환승이 없는 경로 도착시간 구하기") + @Test + void noneTransferAtEndTime() { + // given + List lines = List.of(노선A, 노선B); + List
sections = List.of(구간BC, 구간CD); + String time = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 0)).format(DATE_TIME_PATH); + ArrivalTime arrivalTime = new ArrivalTime(sections, lines, time); + + // when + LocalDateTime value = arrivalTime.value(); + + // then + assertThat(value).isEqualTo(LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(5, 10))); + } + + @DisplayName("막차 시간(23:00) 기준으로 환승이 있는 경로 도착시간 구하기") + @Test + void withTransferAtEndTime() { + // given + List lines = List.of(노선A, 노선B); + List
sections = List.of(구간AB, 구간BE); + String time = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 0)).format(DATE_TIME_PATH); + ArrivalTime arrivalTime = new ArrivalTime(sections, lines, time); + + // when + LocalDateTime value = arrivalTime.value(); + + // then + assertThat(value).isEqualTo(LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(5, 24))); + } +} \ No newline at end of file From 2b73ac28f83e24db17c33810f3e86d6ca44b46bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Wed, 17 Aug 2022 15:41:00 +0900 Subject: [PATCH 08/14] =?UTF-8?q?chore=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/nextstep/subway/unit/SubwayMapTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/nextstep/subway/unit/SubwayMapTest.java b/src/test/java/nextstep/subway/unit/SubwayMapTest.java index a78c6081b..62c515d85 100644 --- a/src/test/java/nextstep/subway/unit/SubwayMapTest.java +++ b/src/test/java/nextstep/subway/unit/SubwayMapTest.java @@ -89,7 +89,6 @@ void findPathOppositely() { private Station createStation(long id, String name) { Station station = new Station(name); ReflectionTestUtils.setField(station, "id", id); - return station; } } From 2fe9fa690f2f565d29ea37279d07857d1a28d783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 29 Aug 2022 23:23:36 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor=20EntitySupplier=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/applicaion/LineService.java | 16 ++++++------ .../subway/applicaion/dto/LineRequest.java | 5 ++-- .../java/nextstep/subway/domain/Line.java | 25 +++++++++++-------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/main/java/nextstep/subway/applicaion/LineService.java b/src/main/java/nextstep/subway/applicaion/LineService.java index aba275055..a9f311d74 100644 --- a/src/main/java/nextstep/subway/applicaion/LineService.java +++ b/src/main/java/nextstep/subway/applicaion/LineService.java @@ -12,6 +12,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.function.Supplier; import java.util.stream.Collectors; @Service @@ -27,7 +28,7 @@ public LineService(LineRepository lineRepository, StationService stationService) @Transactional public LineResponse saveLine(LineRequest request) { - Line line = lineRepository.save(request.toLineEntity()); + Line line = saveLine(request.getEntitySupplier()); if (request.getUpStationId() != null && request.getDownStationId() != null && request.getDistance() != 0) { Station upStation = stationService.findById(request.getUpStationId()); Station downStation = stationService.findById(request.getDownStationId()); @@ -62,14 +63,7 @@ public Line findById(Long id) { @Transactional public void updateLine(Long id, LineRequest lineRequest) { Line line = findById(id); - line.update( - lineRequest.getName(), - lineRequest.getColor(), - lineRequest.getAdditionalFare(), - lineRequest.getStartTime(), - lineRequest.getEndTime(), - lineRequest.getIntervalTime() - ); + line.update(lineRequest.getEntitySupplier()); } @Transactional @@ -93,4 +87,8 @@ public void deleteSection(Long lineId, Long stationId) { line.deleteSection(station); } + + private Line saveLine(Supplier supplier) { + return lineRepository.save(supplier.get()); + } } diff --git a/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java b/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java index 2837efe78..cdce0dbba 100644 --- a/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java +++ b/src/main/java/nextstep/subway/applicaion/dto/LineRequest.java @@ -3,6 +3,7 @@ import nextstep.subway.domain.Line; import java.time.LocalTime; +import java.util.function.Supplier; import static java.time.format.DateTimeFormatter.ISO_TIME; @@ -78,8 +79,8 @@ public int getIntervalTime() { return intervalTime; } - public Line toLineEntity() { - return Line.builder() + public Supplier getEntitySupplier() { + return () -> Line.builder() .name(this.name) .color(this.color) .additionalFare(this.additionalFare) diff --git a/src/main/java/nextstep/subway/domain/Line.java b/src/main/java/nextstep/subway/domain/Line.java index e6a80ceb7..243c4110f 100644 --- a/src/main/java/nextstep/subway/domain/Line.java +++ b/src/main/java/nextstep/subway/domain/Line.java @@ -9,6 +9,7 @@ import javax.persistence.Id; import java.time.LocalTime; import java.util.List; +import java.util.function.Supplier; @Entity public class Line { @@ -81,21 +82,23 @@ public List
getSections() { return sections.getSections(); } - public void update(String name, String color, int additionalFare, LocalTime startTime, LocalTime endTime, int intervalTime) { - if (name != null) { - this.name = name; + public void update(Supplier supplier) { + Line line = supplier.get(); + + if (line.name != null) { + this.name = line.name; } - if (color != null) { - this.color = color; + if (line.color != null) { + this.color = line.color; } - this.additionalFare = additionalFare; - if (startTime != null) { - this.startTime = startTime; + this.additionalFare = line.additionalFare; + if (line.startTime != null) { + this.startTime = line.startTime; } - if (endTime != null) { - this.endTime = endTime; + if (line.endTime != null) { + this.endTime = line.endTime; } - this.intervalTime = intervalTime; + this.intervalTime = line.intervalTime; } public void addSection(Station upStation, Station downStation, int distance, int duration) { From bad7298fb37beb77a5f387c6feec504320813fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 29 Aug 2022 23:24:14 +0900 Subject: [PATCH 10/14] =?UTF-8?q?refactor=20=EC=9A=94=EA=B8=88=20=EC=A0=95?= =?UTF-8?q?=EC=B1=85=20=EC=B2=B4=EC=9D=B8=20=ED=98=95=EC=8B=9D=EC=9D=98=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=EC=84=B1=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../subway/applicaion/PathService.java | 6 +-- .../java/nextstep/subway/domain/Fare.java | 38 +++++++++++++------ .../SubwayMap/ArrivalTimeSubwayMap.java | 5 +-- .../subway/domain/SubwayMap/SubwayMap.java | 6 +-- .../domain/policy/AdditionalFarePolicy.java | 15 ++++++++ .../domain/policy/DiscountFarePolicy.java | 20 ++++++++++ .../DiscountType.java} | 6 +-- .../domain/policy/DistanceFarePolicy.java | 21 ++++++++++ .../DistanceType.java} | 6 +-- .../subway/domain/policy/FarePolicy.java | 20 ++++++++++ ...yTest.java => DiscountFarePolicyTest.java} | 24 ++++++------ .../nextstep/subway/unit/FarePolicyTest.java | 20 +++++----- .../java/nextstep/subway/unit/PathTest.java | 12 +++--- .../nextstep/subway/unit/SubwayMapTest.java | 7 ++-- 14 files changed, 147 insertions(+), 59 deletions(-) create mode 100644 src/main/java/nextstep/subway/domain/policy/AdditionalFarePolicy.java create mode 100644 src/main/java/nextstep/subway/domain/policy/DiscountFarePolicy.java rename src/main/java/nextstep/subway/domain/{DiscountPolicy.java => policy/DiscountType.java} (89%) create mode 100644 src/main/java/nextstep/subway/domain/policy/DistanceFarePolicy.java rename src/main/java/nextstep/subway/domain/{FarePolicy.java => policy/DistanceType.java} (91%) create mode 100644 src/main/java/nextstep/subway/domain/policy/FarePolicy.java rename src/test/java/nextstep/subway/unit/{DiscountPolicyTest.java => DiscountFarePolicyTest.java} (66%) diff --git a/src/main/java/nextstep/subway/applicaion/PathService.java b/src/main/java/nextstep/subway/applicaion/PathService.java index d838dea74..9f78b1c4f 100644 --- a/src/main/java/nextstep/subway/applicaion/PathService.java +++ b/src/main/java/nextstep/subway/applicaion/PathService.java @@ -1,11 +1,10 @@ package nextstep.subway.applicaion; import nextstep.member.application.MemberService; -import nextstep.subway.domain.DiscountPolicy; -import nextstep.subway.domain.PathType; import nextstep.subway.applicaion.dto.PathResponse; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; +import nextstep.subway.domain.PathType; import nextstep.subway.domain.Station; import nextstep.subway.domain.SubwayMap.SubwayMap; import org.springframework.stereotype.Service; @@ -29,9 +28,8 @@ public PathResponse findPath(User user, Long source, Long target, PathType type, Station upStation = stationService.findById(source); Station downStation = stationService.findById(target); List lines = lineService.findLines(); - DiscountPolicy discountPolicy = DiscountPolicy.of(getCurrentUserAge(user.getUsername())); SubwayMap subwayMap = type.getInstance(lines); - Path path = subwayMap.findPath(upStation, downStation, discountPolicy, time); + Path path = subwayMap.findPath(upStation, downStation, getCurrentUserAge(user.getUsername()), time); return PathResponse.of(path); } diff --git a/src/main/java/nextstep/subway/domain/Fare.java b/src/main/java/nextstep/subway/domain/Fare.java index eb48c81cf..6a48caef0 100644 --- a/src/main/java/nextstep/subway/domain/Fare.java +++ b/src/main/java/nextstep/subway/domain/Fare.java @@ -1,23 +1,39 @@ package nextstep.subway.domain; +import nextstep.subway.domain.policy.AdditionalFarePolicy; +import nextstep.subway.domain.policy.DiscountFarePolicy; +import nextstep.subway.domain.policy.DiscountType; +import nextstep.subway.domain.policy.DistanceFarePolicy; +import nextstep.subway.domain.policy.FarePolicy; + public class Fare { - private int totalDistance; - private int additionalFare; - private DiscountPolicy discountPolicy; private FarePolicy farePolicy; - public Fare(int totalDistance, int additionalFare, DiscountPolicy discountPolicy) { - this.totalDistance = totalDistance; - this.additionalFare = additionalFare; - this.discountPolicy = discountPolicy; - this.farePolicy = FarePolicy.of(totalDistance); + public Fare(int distance, int additionalFare, int age) { + this.farePolicy = new DiscountFarePolicy( + age, + new AdditionalFarePolicy( + additionalFare, + new DistanceFarePolicy(distance) + ) + ); + } + + public Fare(int distance, int additionalFare, DiscountType discountType) { + this.farePolicy = new DiscountFarePolicy( + discountType, + new AdditionalFarePolicy( + additionalFare, + new DistanceFarePolicy(distance) + ) + ); } - public Fare(int totalDistance) { - this(totalDistance, 0, DiscountPolicy.ADULT); + public Fare(int distance) { + this(distance, 0, DiscountType.ADULT); } public int value() { - return discountPolicy.discount(additionalFare + farePolicy.calculate(totalDistance)); + return farePolicy.calculate(); } } diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java index 4c54fe55c..5922e917f 100644 --- a/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java +++ b/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java @@ -1,7 +1,6 @@ package nextstep.subway.domain.SubwayMap; import nextstep.subway.domain.ArrivalTime; -import nextstep.subway.domain.DiscountPolicy; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; import nextstep.subway.domain.Section; @@ -22,8 +21,8 @@ public ArrivalTimeSubwayMap(List lines) { } @Override - public Path findPath(Station source, Station target, DiscountPolicy discountPolicy, String time) { - Path path = super.findPath(source, target, discountPolicy, time); + public Path findPath(Station source, Station target, int age, String time) { + Path path = super.findPath(source, target, age, time); ArrivalTime arrivalTime = new ArrivalTime(path.getSections(), lines, time); return new Path(path, arrivalTime); } diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java index 569916a5b..9f65c2bd7 100644 --- a/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java +++ b/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java @@ -1,6 +1,6 @@ package nextstep.subway.domain.SubwayMap; -import nextstep.subway.domain.DiscountPolicy; +import nextstep.subway.domain.policy.DiscountType; import nextstep.subway.domain.Fare; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; @@ -22,7 +22,7 @@ public SubwayMap(List lines) { this.lines = lines; } - public Path findPath(Station source, Station target, DiscountPolicy discountPolicy, String time) { + public Path findPath(Station source, Station target, int age, String time) { // 다익스트라 최단 경로 찾기 GraphPath result = getGraphPath(source, target); @@ -35,7 +35,7 @@ public Path findPath(Station source, Station target, DiscountPolicy discountPoli .max() .orElse(0); - return new Path(sections, new Fare(sections.totalDistance(), maxAdditionalFare, discountPolicy)); + return new Path(sections, new Fare(sections.totalDistance(), maxAdditionalFare, age)); } protected void validateConnectSection(GraphPath result) { diff --git a/src/main/java/nextstep/subway/domain/policy/AdditionalFarePolicy.java b/src/main/java/nextstep/subway/domain/policy/AdditionalFarePolicy.java new file mode 100644 index 000000000..f35ced801 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/policy/AdditionalFarePolicy.java @@ -0,0 +1,15 @@ +package nextstep.subway.domain.policy; + +public class AdditionalFarePolicy extends FarePolicy { + private int additionalFare; + + public AdditionalFarePolicy(int additionalFare, FarePolicy farePolicy) { + super(farePolicy); + this.additionalFare = additionalFare; + } + + @Override + public int calculate() { + return super.calculate() + additionalFare; + } +} diff --git a/src/main/java/nextstep/subway/domain/policy/DiscountFarePolicy.java b/src/main/java/nextstep/subway/domain/policy/DiscountFarePolicy.java new file mode 100644 index 000000000..c7812c2be --- /dev/null +++ b/src/main/java/nextstep/subway/domain/policy/DiscountFarePolicy.java @@ -0,0 +1,20 @@ +package nextstep.subway.domain.policy; + +public class DiscountFarePolicy extends FarePolicy { + private DiscountType discountType; + + public DiscountFarePolicy(int age, FarePolicy farePolicy) { + super(farePolicy); + this.discountType = DiscountType.of(age); + } + + public DiscountFarePolicy(DiscountType discountType, FarePolicy farePolicy) { + super(farePolicy); + this.discountType = discountType; + } + + @Override + public int calculate() { + return discountType.discount(super.calculate()); + } +} diff --git a/src/main/java/nextstep/subway/domain/DiscountPolicy.java b/src/main/java/nextstep/subway/domain/policy/DiscountType.java similarity index 89% rename from src/main/java/nextstep/subway/domain/DiscountPolicy.java rename to src/main/java/nextstep/subway/domain/policy/DiscountType.java index f9325e582..22054b04d 100644 --- a/src/main/java/nextstep/subway/domain/DiscountPolicy.java +++ b/src/main/java/nextstep/subway/domain/policy/DiscountType.java @@ -1,6 +1,6 @@ -package nextstep.subway.domain; +package nextstep.subway.domain.policy; -public enum DiscountPolicy { +public enum DiscountType { BABY { @Override public int discount(int fare) { @@ -28,7 +28,7 @@ public int discount(int fare) { public static final String INVALID_AGE_MESSAGE = "It starts from 1 year old in Korean age."; - public static DiscountPolicy of(int age) { + public static DiscountType of(int age) { if (age <= 0) { throw new IllegalArgumentException(INVALID_AGE_MESSAGE); } diff --git a/src/main/java/nextstep/subway/domain/policy/DistanceFarePolicy.java b/src/main/java/nextstep/subway/domain/policy/DistanceFarePolicy.java new file mode 100644 index 000000000..bd674842e --- /dev/null +++ b/src/main/java/nextstep/subway/domain/policy/DistanceFarePolicy.java @@ -0,0 +1,21 @@ +package nextstep.subway.domain.policy; + +public class DistanceFarePolicy extends FarePolicy { + private DistanceType distanceType; + private int distance; + + public DistanceFarePolicy(int distance) { + this(distance, null); + } + + public DistanceFarePolicy(int distance, FarePolicy farePolicy) { + super(farePolicy); + this.distance = distance; + this.distanceType = DistanceType.of(distance); + } + + @Override + public int calculate() { + return distanceType.calculate(distance); + } +} diff --git a/src/main/java/nextstep/subway/domain/FarePolicy.java b/src/main/java/nextstep/subway/domain/policy/DistanceType.java similarity index 91% rename from src/main/java/nextstep/subway/domain/FarePolicy.java rename to src/main/java/nextstep/subway/domain/policy/DistanceType.java index 97fdfdb94..302b10371 100644 --- a/src/main/java/nextstep/subway/domain/FarePolicy.java +++ b/src/main/java/nextstep/subway/domain/policy/DistanceType.java @@ -1,6 +1,6 @@ -package nextstep.subway.domain; +package nextstep.subway.domain.policy; -public enum FarePolicy { +public enum DistanceType { DEFAULT { @Override public int calculate(int distance) { @@ -27,7 +27,7 @@ public int calculate(int distance) { public static final int EVERY_5KM = 5; public static final int EVERY_8KM = 8; - public static FarePolicy of(int distance) { + public static DistanceType of(int distance) { if (distance < TEN_KILO_METER) { return DEFAULT; } diff --git a/src/main/java/nextstep/subway/domain/policy/FarePolicy.java b/src/main/java/nextstep/subway/domain/policy/FarePolicy.java new file mode 100644 index 000000000..23c4fa2c5 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/policy/FarePolicy.java @@ -0,0 +1,20 @@ +package nextstep.subway.domain.policy; + +public abstract class FarePolicy { + private FarePolicy farePolicy; + + public FarePolicy() { + this(null); + } + + public FarePolicy(FarePolicy farePolicy) { + this.farePolicy = farePolicy; + } + + public int calculate() { + if (farePolicy != null) { + return farePolicy.calculate(); + } + return 0; + } +} diff --git a/src/test/java/nextstep/subway/unit/DiscountPolicyTest.java b/src/test/java/nextstep/subway/unit/DiscountFarePolicyTest.java similarity index 66% rename from src/test/java/nextstep/subway/unit/DiscountPolicyTest.java rename to src/test/java/nextstep/subway/unit/DiscountFarePolicyTest.java index bdb8a43f6..2c6065571 100644 --- a/src/test/java/nextstep/subway/unit/DiscountPolicyTest.java +++ b/src/test/java/nextstep/subway/unit/DiscountFarePolicyTest.java @@ -1,57 +1,57 @@ package nextstep.subway.unit; -import nextstep.subway.domain.DiscountPolicy; +import nextstep.subway.domain.policy.DiscountType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static nextstep.subway.domain.DiscountPolicy.INVALID_AGE_MESSAGE; -import static nextstep.subway.domain.FarePolicy.DEFAULT_FARE; +import static nextstep.subway.domain.policy.DiscountType.INVALID_AGE_MESSAGE; +import static nextstep.subway.domain.policy.DistanceType.DEFAULT_FARE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @DisplayName("나이별 요금 할인 정책 테스트") -class DiscountPolicyTest { +class DiscountFarePolicyTest { @DisplayName("5세 이하 영유아는 무료") @Test void baby() { - DiscountPolicy discountPolicy = DiscountPolicy.of(5); + DiscountType discountPolicy = DiscountType.of(5); assertThat(discountPolicy.discount(DEFAULT_FARE)).isEqualTo(0); } @DisplayName("6세 이상 13세 미만 어린이는 운임에서 350원을 공제한 금액의 50%할인") @Test void children() { - DiscountPolicy ageOf6 = DiscountPolicy.of(6); + DiscountType ageOf6 = DiscountType.of(6); assertThat(ageOf6.discount(DEFAULT_FARE)).isEqualTo(450); - DiscountPolicy ageOf12 = DiscountPolicy.of(12); + DiscountType ageOf12 = DiscountType.of(12); assertThat(ageOf12.discount(DEFAULT_FARE)).isEqualTo(450); } @DisplayName("13세 이상 19세 미만 청소년은 운임에서 350원을 공제한 금액의 20%할인") @Test void teenager() { - DiscountPolicy ageOf13 = DiscountPolicy.of(13); + DiscountType ageOf13 = DiscountType.of(13); assertThat(ageOf13.discount(DEFAULT_FARE)).isEqualTo(720); - DiscountPolicy ageOf18 = DiscountPolicy.of(18); + DiscountType ageOf18 = DiscountType.of(18); assertThat(ageOf18.discount(DEFAULT_FARE)).isEqualTo(720); } @DisplayName("19세 이상 어른은 기본 운임비") @Test void adult() { - DiscountPolicy ageOf19 = DiscountPolicy.of(19); + DiscountType ageOf19 = DiscountType.of(19); assertThat(ageOf19.discount(DEFAULT_FARE)).isEqualTo(1250); } @DisplayName("0 또는 음수는 예외 발생") @Test void exception() { - assertThatThrownBy(() -> DiscountPolicy.of(0), INVALID_AGE_MESSAGE) + assertThatThrownBy(() -> DiscountType.of(0), INVALID_AGE_MESSAGE) .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy(() -> DiscountPolicy.of(-1), INVALID_AGE_MESSAGE) + assertThatThrownBy(() -> DiscountType.of(-1), INVALID_AGE_MESSAGE) .isInstanceOf(IllegalArgumentException.class); } } \ No newline at end of file diff --git a/src/test/java/nextstep/subway/unit/FarePolicyTest.java b/src/test/java/nextstep/subway/unit/FarePolicyTest.java index 979334d35..6f76db8a9 100644 --- a/src/test/java/nextstep/subway/unit/FarePolicyTest.java +++ b/src/test/java/nextstep/subway/unit/FarePolicyTest.java @@ -1,10 +1,10 @@ package nextstep.subway.unit; -import nextstep.subway.domain.FarePolicy; +import nextstep.subway.domain.policy.DistanceType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static nextstep.subway.domain.FarePolicy.DEFAULT_FARE; +import static nextstep.subway.domain.policy.DistanceType.DEFAULT_FARE; import static org.assertj.core.api.Assertions.assertThat; @DisplayName("요금 정책 관련 테스트") @@ -13,28 +13,28 @@ class FarePolicyTest { @DisplayName("10KM 요금 계산") @Test void defaultFare() { - FarePolicy farePolicy = FarePolicy.of(9); - assertThat(farePolicy.calculate(9)).isEqualTo(DEFAULT_FARE); + DistanceType distanceType = DistanceType.of(9); + assertThat(distanceType.calculate(9)).isEqualTo(DEFAULT_FARE); } @DisplayName("10km 초과 요금 조회") @Test void over10KmFare() { - FarePolicy farePolicy = FarePolicy.of(10); - assertThat(farePolicy.calculate(10)).isEqualTo(DEFAULT_FARE + 100); + DistanceType distanceType = DistanceType.of(10); + assertThat(distanceType.calculate(10)).isEqualTo(DEFAULT_FARE + 100); } @DisplayName("49km 요금 조회") @Test void over49KmFare() { - FarePolicy farePolicy = FarePolicy.of(49); - assertThat(farePolicy.calculate(49)).isEqualTo(DEFAULT_FARE + 800); + DistanceType distanceType = DistanceType.of(49); + assertThat(distanceType.calculate(49)).isEqualTo(DEFAULT_FARE + 800); } @DisplayName("50km 초과 요금 조회") @Test void over50KmFare() { - FarePolicy farePolicy = FarePolicy.of(50); - assertThat(farePolicy.calculate(50)).isEqualTo(DEFAULT_FARE + 800 + 100); + DistanceType distanceType = DistanceType.of(50); + assertThat(distanceType.calculate(50)).isEqualTo(DEFAULT_FARE + 800 + 100); } } \ No newline at end of file diff --git a/src/test/java/nextstep/subway/unit/PathTest.java b/src/test/java/nextstep/subway/unit/PathTest.java index 32d6b5c06..81e07b902 100644 --- a/src/test/java/nextstep/subway/unit/PathTest.java +++ b/src/test/java/nextstep/subway/unit/PathTest.java @@ -1,6 +1,6 @@ package nextstep.subway.unit; -import nextstep.subway.domain.DiscountPolicy; +import nextstep.subway.domain.policy.DiscountType; import nextstep.subway.domain.Fare; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; @@ -12,7 +12,7 @@ import java.util.List; -import static nextstep.subway.domain.FarePolicy.DEFAULT_FARE; +import static nextstep.subway.domain.policy.DistanceType.DEFAULT_FARE; import static org.assertj.core.api.Assertions.assertThat; @DisplayName("경로 조회 테스트") @@ -76,7 +76,7 @@ void over50KmFare() { void defaultFareWith_신분당선() { Section AB구간 = new Section(신분당선, 지하철A역, 지하철B역, 9, 3); Sections sections = new Sections(List.of(AB구간)); - Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountPolicy.ADULT)); + Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountType.ADULT)); final int 요금_2150원 = DEFAULT_FARE + 신분당선.getAdditionalFare(); final int 거리_9km = 9; @@ -88,7 +88,7 @@ void over50KmFare() { void over10KmFareWith_신분당선() { Section AB구간 = new Section(신분당선, 지하철A역, 지하철B역, 10, 3); Sections sections = new Sections(List.of(AB구간)); - Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountPolicy.ADULT)); + Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountType.ADULT)); final int 요금_2250원 = DEFAULT_FARE + 신분당선.getAdditionalFare() + 100; final int 거리_10km = 10; @@ -100,7 +100,7 @@ void over50KmFare() { void over49KmFareWith_신분당선() { Section AB구간 = new Section(신분당선, 지하철A역, 지하철B역, 49, 3); Sections sections = new Sections(List.of(AB구간)); - Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountPolicy.ADULT)); + Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountType.ADULT)); final int 요금_2950원 = DEFAULT_FARE + 신분당선.getAdditionalFare() + 800; final int 거리_49km = 49; @@ -112,7 +112,7 @@ void over50KmFare() { void over50KmFareWith_신분당선() { Section AB구간 = new Section(신분당선, 지하철A역, 지하철B역, 50, 3); Sections sections = new Sections(List.of(AB구간)); - Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountPolicy.ADULT)); + Path path = new Path(sections, new Fare(sections.totalDistance(), 신분당선.getAdditionalFare(), DiscountType.ADULT)); final int 요금_3050원 = DEFAULT_FARE + 신분당선.getAdditionalFare() + 800 + 100; final int 거리_50km = 50; diff --git a/src/test/java/nextstep/subway/unit/SubwayMapTest.java b/src/test/java/nextstep/subway/unit/SubwayMapTest.java index 62c515d85..5d836f7b7 100644 --- a/src/test/java/nextstep/subway/unit/SubwayMapTest.java +++ b/src/test/java/nextstep/subway/unit/SubwayMapTest.java @@ -1,6 +1,5 @@ package nextstep.subway.unit; -import nextstep.subway.domain.DiscountPolicy; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; import nextstep.subway.domain.PathType; @@ -51,7 +50,7 @@ void findPathWithDistance() { SubwayMap subwayMap = PathType.DISTANCE.getInstance(lines); // when - Path path = subwayMap.findPath(교대역, 양재역, DiscountPolicy.ADULT, null); + Path path = subwayMap.findPath(교대역, 양재역, 20, null); // then assertThat(path.getStations()).containsExactlyElementsOf(Lists.newArrayList(교대역, 강남역, 양재역)); @@ -65,7 +64,7 @@ void findPathWithDuration() { SubwayMap subwayMap = PathType.DURATION.getInstance(lines); // when - Path path = subwayMap.findPath(교대역, 양재역, DiscountPolicy.ADULT, null); + Path path = subwayMap.findPath(교대역, 양재역, 20, null); // then assertThat(path.getStations()).containsExactlyElementsOf(Lists.newArrayList(교대역, 남부터미널역, 양재역)); @@ -79,7 +78,7 @@ void findPathOppositely() { SubwayMap subwayMap = PathType.DISTANCE.getInstance(lines); // when - Path path = subwayMap.findPath(양재역, 교대역, DiscountPolicy.ADULT, null); + Path path = subwayMap.findPath(양재역, 교대역, 20, null); // then assertThat(path.getStations()).containsExactlyElementsOf(Lists.newArrayList(양재역, 강남역, 교대역)); From 107ac859847f418bac7b1d29283674b91c13f659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 29 Aug 2022 23:49:56 +0900 Subject: [PATCH 11/14] =?UTF-8?q?refactor=20=EC=9D=BC=EA=B8=89=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/subway/domain/ArrivalTime.java | 15 ++++++--------- .../java/nextstep/subway/domain/Lines.java | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 src/main/java/nextstep/subway/domain/Lines.java diff --git a/src/main/java/nextstep/subway/domain/ArrivalTime.java b/src/main/java/nextstep/subway/domain/ArrivalTime.java index ccabcba2f..247705924 100644 --- a/src/main/java/nextstep/subway/domain/ArrivalTime.java +++ b/src/main/java/nextstep/subway/domain/ArrivalTime.java @@ -8,14 +8,14 @@ import static nextstep.support.entity.Formatters.DATE_TIME_PATH; public class ArrivalTime { - private List
sections; - private List lines; + private Sections sections; + private Lines lines; private LocalDateTime dateTime; private LocalTime startTime; public ArrivalTime(List
sections, List lines, String time) { - this.sections = sections; - this.lines = lines; + this.sections = new Sections(sections); + this.lines = new Lines(lines); this.dateTime = LocalDateTime.parse(time, DATE_TIME_PATH); this.startTime = findLine(sections.get(0)).getStartTime(); } @@ -28,7 +28,7 @@ private LocalDateTime getTimeOfFastestPath(LocalDateTime dateTime) { LocalDateTime currentDateTime = dateTime; Line currentLine = null; - for (Section section : sections) { + for (Section section : sections.getSections()) { Line line = findLine(section); if (!line.equals(currentLine)) { currentLine = line; @@ -62,10 +62,7 @@ private LocalDateTime calculateTimeInBeforeStations(LocalDateTime currentDateTim } private Line findLine(Section section) { - return lines.stream() - .filter(item -> item.getSections().contains(section)) - .findFirst() - .get(); + return lines.indexOf(section); } private LocalDateTime changeTime(LocalDateTime dateTime, LocalTime time) { diff --git a/src/main/java/nextstep/subway/domain/Lines.java b/src/main/java/nextstep/subway/domain/Lines.java new file mode 100644 index 000000000..b95160c11 --- /dev/null +++ b/src/main/java/nextstep/subway/domain/Lines.java @@ -0,0 +1,18 @@ +package nextstep.subway.domain; + +import java.util.List; + +public class Lines { + private List lines; + + public Lines(List lines) { + this.lines = lines; + } + + public Line indexOf(Section section) { + return lines.stream() + .filter(item -> item.getSections().contains(section)) + .findFirst() + .get(); + } +} From c1a310c00e05f91418fd0d2af37a2d979de0d81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Mon, 29 Aug 2022 23:50:16 +0900 Subject: [PATCH 12/14] =?UTF-8?q?refactor=20=EB=A9=94=EC=86=8C=EB=93=9C=20?= =?UTF-8?q?=ED=85=9C=ED=94=8C=EB=A6=BF=20=ED=8C=A8=ED=84=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubwayMap/ArrivalTimeSubwayMap.java | 52 +++++-------------- .../domain/SubwayMap/DistanceSubwayMap.java | 50 +++++------------- .../domain/SubwayMap/DurationSubwayMap.java | 50 +++++------------- .../subway/domain/SubwayMap/SubwayMap.java | 40 +++++++++++++- 4 files changed, 77 insertions(+), 115 deletions(-) diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java index 5922e917f..e171106f7 100644 --- a/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java +++ b/src/main/java/nextstep/subway/domain/SubwayMap/ArrivalTimeSubwayMap.java @@ -8,14 +8,17 @@ import nextstep.subway.domain.Station; import org.jgrapht.GraphPath; import org.jgrapht.alg.shortestpath.KShortestPaths; +import org.jgrapht.graph.AbstractBaseGraph; import org.jgrapht.graph.Multigraph; import java.util.Comparator; import java.util.List; -import java.util.stream.Collectors; +import java.util.function.Consumer; public class ArrivalTimeSubwayMap extends SubwayMap { + public static final int MAX_PASS_STATION_COUNT = 100; + public ArrivalTimeSubwayMap(List lines) { super(lines); } @@ -29,48 +32,21 @@ public Path findPath(Station source, Station target, int age, String time) { @Override protected GraphPath getGraphPath(Station source, Station target) { - KShortestPaths kShortestPaths = new KShortestPaths(getGraph(), 100); + KShortestPaths kShortestPaths = new KShortestPaths( + getGraph(() -> new Multigraph<>(SectionEdge.class)), MAX_PASS_STATION_COUNT); List paths = kShortestPaths.getPaths(source, target); return paths.stream() .min(Comparator.comparing(path -> path.getWeight())) .orElse(null); } - private Multigraph getGraph() { - Multigraph graph = new Multigraph<>(SectionEdge.class); - - // 지하철 역(정점)을 등록 - lines.stream() - .flatMap(it -> it.getStations().stream()) - .distinct() - .collect(Collectors.toList()) - .forEach(it -> graph.addVertex(it)); - - // 지하철 역의 연결 정보(간선)을 등록 - lines.stream() - .flatMap(it -> it.getSections().stream()) - .forEach(it -> { - SectionEdge sectionEdge = SectionEdge.of(it); - graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); - graph.setEdgeWeight(sectionEdge, it.getDuration()); - }); - - // 지하철 역의 연결 정보(간선)을 등록 - lines.stream() - .flatMap(it -> it.getSections().stream()) - .map(it -> new Section( - it.getLine(), - it.getDownStation(), - it.getUpStation(), - it.getDistance(), - it.getDuration() - ) - ) - .forEach(it -> { - SectionEdge sectionEdge = SectionEdge.of(it); - graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); - graph.setEdgeWeight(sectionEdge, it.getDuration()); - }); - return graph; + @Override + protected Consumer
getConnectSectionConsumer(AbstractBaseGraph graph) { + return it -> { + SectionEdge sectionEdge = SectionEdge.of(it); + graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); + graph.setEdgeWeight(sectionEdge, it.getDuration()); + }; } + } diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/DistanceSubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/DistanceSubwayMap.java index 91f63bd98..9d435884d 100644 --- a/src/main/java/nextstep/subway/domain/SubwayMap/DistanceSubwayMap.java +++ b/src/main/java/nextstep/subway/domain/SubwayMap/DistanceSubwayMap.java @@ -6,10 +6,11 @@ import nextstep.subway.domain.Station; import org.jgrapht.GraphPath; import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.AbstractBaseGraph; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import java.util.List; -import java.util.stream.Collectors; +import java.util.function.Consumer; public class DistanceSubwayMap extends SubwayMap { public DistanceSubwayMap(List lines) { @@ -18,45 +19,18 @@ public DistanceSubwayMap(List lines) { @Override protected GraphPath getGraphPath(Station source, Station target) { - DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(getGraph()); + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>( + getGraph(() -> new SimpleDirectedWeightedGraph<>(SectionEdge.class))); return dijkstraShortestPath.getPath(source, target); } - private SimpleDirectedWeightedGraph getGraph() { - SimpleDirectedWeightedGraph graph = new SimpleDirectedWeightedGraph<>(SectionEdge.class); - - // 지하철 역(정점)을 등록 - lines.stream() - .flatMap(it -> it.getStations().stream()) - .distinct() - .collect(Collectors.toList()) - .forEach(it -> graph.addVertex(it)); - - // 지하철 역의 연결 정보(간선)을 등록 - lines.stream() - .flatMap(it -> it.getSections().stream()) - .forEach(it -> { - SectionEdge sectionEdge = SectionEdge.of(it); - graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); - graph.setEdgeWeight(sectionEdge, it.getDistance()); - }); - - // 지하철 역의 연결 정보(간선)을 등록 - lines.stream() - .flatMap(it -> it.getSections().stream()) - .map(it -> new Section( - it.getLine(), - it.getDownStation(), - it.getUpStation(), - it.getDistance(), - it.getDuration() - ) - ) - .forEach(it -> { - SectionEdge sectionEdge = SectionEdge.of(it); - graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); - graph.setEdgeWeight(sectionEdge, it.getDistance()); - }); - return graph; + @Override + protected Consumer
getConnectSectionConsumer(AbstractBaseGraph graph) { + return it -> { + SectionEdge sectionEdge = SectionEdge.of(it); + graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); + graph.setEdgeWeight(sectionEdge, it.getDistance()); + }; } + } diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/DurationSubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/DurationSubwayMap.java index 49f797855..4d316a504 100644 --- a/src/main/java/nextstep/subway/domain/SubwayMap/DurationSubwayMap.java +++ b/src/main/java/nextstep/subway/domain/SubwayMap/DurationSubwayMap.java @@ -6,10 +6,11 @@ import nextstep.subway.domain.Station; import org.jgrapht.GraphPath; import org.jgrapht.alg.shortestpath.DijkstraShortestPath; +import org.jgrapht.graph.AbstractBaseGraph; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import java.util.List; -import java.util.stream.Collectors; +import java.util.function.Consumer; public class DurationSubwayMap extends SubwayMap { @@ -19,45 +20,18 @@ public DurationSubwayMap(List lines) { @Override protected GraphPath getGraphPath(Station source, Station target) { - DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(getGraph()); + DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>( + getGraph(() -> new SimpleDirectedWeightedGraph<>(SectionEdge.class))); return dijkstraShortestPath.getPath(source, target); } - private SimpleDirectedWeightedGraph getGraph() { - SimpleDirectedWeightedGraph graph = new SimpleDirectedWeightedGraph<>(SectionEdge.class); - - // 지하철 역(정점)을 등록 - lines.stream() - .flatMap(it -> it.getStations().stream()) - .distinct() - .collect(Collectors.toList()) - .forEach(it -> graph.addVertex(it)); - - // 지하철 역의 연결 정보(간선)을 등록 - lines.stream() - .flatMap(it -> it.getSections().stream()) - .forEach(it -> { - SectionEdge sectionEdge = SectionEdge.of(it); - graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); - graph.setEdgeWeight(sectionEdge, it.getDuration()); - }); - - // 지하철 역의 연결 정보(간선)을 등록 - lines.stream() - .flatMap(it -> it.getSections().stream()) - .map(it -> new Section( - it.getLine(), - it.getDownStation(), - it.getUpStation(), - it.getDistance(), - it.getDuration() - ) - ) - .forEach(it -> { - SectionEdge sectionEdge = SectionEdge.of(it); - graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); - graph.setEdgeWeight(sectionEdge, it.getDuration()); - }); - return graph; + @Override + protected Consumer
getConnectSectionConsumer(AbstractBaseGraph graph) { + return it -> { + SectionEdge sectionEdge = SectionEdge.of(it); + graph.addEdge(it.getUpStation(), it.getDownStation(), sectionEdge); + graph.setEdgeWeight(sectionEdge, it.getDuration()); + }; } + } diff --git a/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java b/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java index 9f65c2bd7..d55a30dee 100644 --- a/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java +++ b/src/main/java/nextstep/subway/domain/SubwayMap/SubwayMap.java @@ -1,6 +1,5 @@ package nextstep.subway.domain.SubwayMap; -import nextstep.subway.domain.policy.DiscountType; import nextstep.subway.domain.Fare; import nextstep.subway.domain.Line; import nextstep.subway.domain.Path; @@ -9,9 +8,13 @@ import nextstep.subway.domain.Sections; import nextstep.subway.domain.Station; import nextstep.subway.exception.NotConnectSectionException; +import org.jgrapht.Graph; import org.jgrapht.GraphPath; +import org.jgrapht.graph.AbstractBaseGraph; import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -49,5 +52,40 @@ protected Stream
streamSections(GraphPath result) .map(SectionEdge::getSection); } + protected AbstractBaseGraph getGraph(Supplier> graphSupplier) { + AbstractBaseGraph graph = graphSupplier.get(); + setupStations(graph); + connectSections(getConnectSectionConsumer(graph)); + return graph; + } + + protected void setupStations(Graph graph) { + lines.stream() + .flatMap(it -> it.getStations().stream()) + .distinct() + .collect(Collectors.toList()) + .forEach(it -> graph.addVertex(it)); + } + + protected void connectSections(Consumer
sectionConsumer) { + lines.stream() + .flatMap(it -> it.getSections().stream()) + .forEach(sectionConsumer); + + lines.stream() + .flatMap(it -> it.getSections().stream()) + .map(it -> new Section( + it.getLine(), + it.getDownStation(), + it.getUpStation(), + it.getDistance(), + it.getDuration() + ) + ) + .forEach(sectionConsumer); + } + protected abstract GraphPath getGraphPath(Station source, Station target); + + protected abstract Consumer
getConnectSectionConsumer(AbstractBaseGraph graph); } From 8ae08fef635e3aa80896d0d90268621656f70987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Tue, 30 Aug 2022 22:30:58 +0900 Subject: [PATCH 13/14] =?UTF-8?q?refactor=20=EC=B5=9C=EB=8B=A8=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EB=8F=84=EC=B0=A9=EA=B2=BD=EB=A1=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20depth=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nextstep/subway/domain/ArrivalTime.java | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/main/java/nextstep/subway/domain/ArrivalTime.java b/src/main/java/nextstep/subway/domain/ArrivalTime.java index 247705924..1f2617cc5 100644 --- a/src/main/java/nextstep/subway/domain/ArrivalTime.java +++ b/src/main/java/nextstep/subway/domain/ArrivalTime.java @@ -13,6 +13,9 @@ public class ArrivalTime { private LocalDateTime dateTime; private LocalTime startTime; + private LocalDateTime currentDateTime; + private Line currentLine = null; + public ArrivalTime(List
sections, List lines, String time) { this.sections = new Sections(sections); this.lines = new Lines(lines); @@ -25,36 +28,56 @@ public LocalDateTime value() { } private LocalDateTime getTimeOfFastestPath(LocalDateTime dateTime) { - LocalDateTime currentDateTime = dateTime; - Line currentLine = null; + this.currentDateTime = dateTime; for (Section section : sections.getSections()) { - Line line = findLine(section); - if (!line.equals(currentLine)) { - currentLine = line; - currentDateTime = calculateTimeInBeforeStations(currentDateTime, currentLine, section); - - if (goingLastTime(currentDateTime, currentLine.getEndTime())) { - return getTimeOfFirstPath(); - } + if (confirmPathCondition(section)) { + return getTimeOfFirstPath(); } - - currentDateTime = currentDateTime.plusMinutes(section.getDuration()); } return currentDateTime; } + private boolean confirmPathCondition(Section section) { + Line line = findLine(section); + boolean notSameLine = !line.equals(currentLine); + + setupCurrentDateTime(notSameLine, line, section); + if (notSameLine && goingLastTime(line.getEndTime())) { + return true; + } + setupCurrentLine(notSameLine, line); + return false; + } + + private void setupCurrentDateTime(boolean notSameLine, Line line, Section section) { + if (notSameLine) { + this.currentDateTime = calculateTimeInBeforeStations(line, section); + } + this.currentDateTime = currentDateTime.plusMinutes(section.getDuration()); + } + + private void setupCurrentLine(boolean notSameLine, Line line) { + if (notSameLine) { + this.currentLine = line; + } + } + + private LocalDateTime getCurrentDateTime(boolean notSameLine) { + return null; + } + private LocalDateTime getTimeOfFirstPath() { LocalDateTime nextDayAtStartDateTime = changeTime(dateTime.plusDays(1), startTime); return getTimeOfFastestPath(nextDayAtStartDateTime); } - private boolean goingLastTime(LocalDateTime dateTime, LocalTime endTime) { - return dateTime.isAfter(changeTime(dateTime, endTime)); + private boolean goingLastTime(LocalTime endTime) { + return currentDateTime.isAfter(changeTime(currentDateTime, endTime)); } - private LocalDateTime calculateTimeInBeforeStations(LocalDateTime currentDateTime, Line currentLine, Section section) { + private LocalDateTime calculateTimeInBeforeStations(Line currentLine, Section section) { LocalDateTime targetTime = changeTime(currentDateTime, currentLine.getStartTime()); LocalDateTime fastestDepartureTime = findFastestDepartureTime(currentDateTime, targetTime, currentLine.getIntervalTime()); int beforeDuration = getBeforeDuration(currentLine, section); From fd1b61ce22273ea833862d53bd030d876f8787bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=84=EB=AA=85=EC=84=AD?= Date: Tue, 30 Aug 2022 22:44:35 +0900 Subject: [PATCH 14/14] =?UTF-8?q?chore=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/nextstep/subway/domain/ArrivalTime.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/java/nextstep/subway/domain/ArrivalTime.java b/src/main/java/nextstep/subway/domain/ArrivalTime.java index 1f2617cc5..68c637353 100644 --- a/src/main/java/nextstep/subway/domain/ArrivalTime.java +++ b/src/main/java/nextstep/subway/domain/ArrivalTime.java @@ -64,10 +64,6 @@ private void setupCurrentLine(boolean notSameLine, Line line) { } } - private LocalDateTime getCurrentDateTime(boolean notSameLine) { - return null; - } - private LocalDateTime getTimeOfFirstPath() { LocalDateTime nextDayAtStartDateTime = changeTime(dateTime.plusDays(1), startTime); return getTimeOfFastestPath(nextDayAtStartDateTime); @@ -110,11 +106,4 @@ private LocalDateTime findFastestDepartureTime(LocalDateTime referenceTime, Loca } return targetTime; } - - private LocalTime findTimeRequired(LocalTime baseTime, LocalTime time, int interval) { - if (baseTime.isAfter(time)) { - return findTimeRequired(baseTime, time.plusMinutes(interval), interval); - } - return time; - } }