Skip to content

Commit

Permalink
[김대겸] Step2 PR (#765)
Browse files Browse the repository at this point in the history
* refactor(sections): Optional 리팩토링

* refactor(fixture): fixture 리팩토링

* docs(readme): step2 요구사항 작성

* refactor(test): 인수테스트 분리

* test(path): 최단경로 조회 인수 테스트 작성

* feat(controller): PathController 기능 및 테스트 추가

* feat(service): PathService 기능 및 테스트 추가

* feat(pathFinder): PathFinder 기능 및 테스트 추가

* refactor: 주 생성자 및 부 생성자 리팩토링

* test(pathfinder): 출발역과 도착역이 같은 경우 인수 테스트 작성

* feat(pathfinder): 출발역과 도착역이 같은 경우 예외처리 추가

* feat(pathfinder): 출발역과 도착역이 연결이 되어 있지 않은 경우 예외처리 추가

* feat(pathfinder): 존재하지 않은 출발역이나 도착역을 조회 할 경우 예외처리 추가
  • Loading branch information
Gyeom committed Nov 27, 2022
1 parent 0b3e571 commit bb35344
Show file tree
Hide file tree
Showing 24 changed files with 967 additions and 323 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,22 @@
- [X] Domain의 단위 테스트를 작성하기
- [X] 새로운 로직 만들기
- [X] 정상 동작 확인 후 기존 로직 제거


# 🚀 2단계 - 경로 조회 기능

## 요구사항 기능 체크리스트
- [X] 최단 경로 조회 기능 구현하기
- [X] 최단 경로 조회 인수 테스트 작성
- [X] 최단 경로 조회 기능 추가
- [X] 예외 사항 Validation 기능 구현하기
- [X] 출발역과 도착역이 같은 경우
- [X] 인수 테스트 작성
- [X] 도메인 테스트 작성
- [X] Validation 기능 구현
- [X] 출발역과 도착역이 연결이 되어 있지 않은 경우
- [X] 인수 테스트 작성
- [X] 도메인 테스트 작성
- [X] Validation 기능 구현
- [X] 존재하지 않은 출발역이나 도착역을 조회 할 경우
- [X] 인수 테스트 작성
- [X] Validation 기능 구현
7 changes: 4 additions & 3 deletions src/main/java/nextstep/subway/line/domain/Distance.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ public class Distance {
@Column(name = "distance")
private int value;

public Distance(final int value) {
this.value = value;
protected Distance() {
this(0);
}

protected Distance() {
public Distance(final int value) {
this.value = value;
}

int value() {
Expand Down
27 changes: 15 additions & 12 deletions src/main/java/nextstep/subway/line/domain/Line.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
import nextstep.subway.station.domain.Station;

import javax.persistence.*;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.*;

@Entity
public class Line extends BaseEntity {
Expand All @@ -22,13 +19,11 @@ public class Line extends BaseEntity {
private final Sections sections;

public Line() {
this.sections = new Sections();
this(null, null, null, null, 0);
}

public Line(String name, String color) {
this.name = name;
this.color = color;
this.sections = new Sections();
this(name, color, null, null, 0);
}

public Line(String name, String color, Station upStation, Station downStation, int distance) {
Expand Down Expand Up @@ -58,6 +53,10 @@ public Set<Station> getStations() {
return sections.getStations();
}

public List<Section> getSections() {
return sections.value();
}

public void addSection(final Section section) {
validateSection(section);
if (addInitialSection(section)) return;
Expand All @@ -67,16 +66,20 @@ public void addSection(final Section section) {

private void addDownSection(final Section section) {
if (isStationExisted(section.downStation())) {
Optional<Section> originalSection = sections.findSameDownStation(section);
originalSection.ifPresent(it -> it.updateDownStation(section));
Section originalSection = sections.findSameDownStation(section);
if (Objects.nonNull(originalSection)) {
originalSection.updateDownStation(section);
}
sections.add(section, this::syncLine);
}
}

private boolean addUpSection(final Section section) {
if (isStationExisted(section.upStation())) {
Optional<Section> originalSection = sections.findSameUpStation(section);
originalSection.ifPresent(it -> it.updateUpStation(section));
Section originalSection = sections.findSameUpStation(section);
if (Objects.nonNull(originalSection)) {
originalSection.updateUpStation(section);
}
sections.add(section, this::syncLine);
return true;
}
Expand Down
13 changes: 6 additions & 7 deletions src/main/java/nextstep/subway/line/domain/Section.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,16 @@ public class Section {
@Embedded
Distance distance;

public Section() {
protected Section() {
this(null, null, 0);
}

public Section(Line line, Station upStation, Station downStation, int distance) {
this.line = line;
this.upStation = upStation;
this.downStation = downStation;
this.distance = new Distance(distance);
public Section(final Station upStation, final Station downStation, final int distance) {
this(null, upStation, downStation, distance);
}

public Section(Station upStation, Station downStation, int distance) {
public Section(final Line line, final Station upStation, final Station downStation, final int distance) {
this.line = line;
this.upStation = upStation;
this.downStation = downStation;
this.distance = new Distance(distance);
Expand Down
59 changes: 35 additions & 24 deletions src/main/java/nextstep/subway/line/domain/Sections.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class Sections {
@OneToMany(mappedBy = "line", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
private final List<Section> sections = new ArrayList<>();

public Sections() {
protected Sections() {
}

public Sections(final Section section) {
Expand All @@ -24,29 +24,31 @@ public Sections(final Section section) {

public Set<Station> getStations() {
Set<Station> sortedStations = new LinkedHashSet<>();
Optional<Section> optionalSection = findFirstSection();
while (optionalSection.isPresent()) {
Section section = optionalSection.get();
Section optionalSection = findFirstSection();
while (Objects.nonNull(optionalSection)) {
Section section = optionalSection;
sortedStations.addAll(section.stations());
optionalSection = findNextSection(section);
}
return sortedStations;
}

private Optional<Section> findFirstSection() {
private Section findFirstSection() {
List<Station> downStations = this.sections.stream()
.map(Section::downStation)
.collect(Collectors.toList());

return this.sections.stream()
.filter(section -> !downStations.contains(section.upStation()))
.findFirst();
.findFirst()
.orElse(null);
}

private Optional<Section> findNextSection(final Section currentSection) {
private Section findNextSection(final Section currentSection) {
return this.sections.stream()
.filter(section -> section.isNextOf(currentSection))
.findFirst();
.findFirst()
.orElse(null);
}

public List<Section> value() {
Expand All @@ -58,16 +60,18 @@ public void add(final Section section, final Consumer<Section> syncLine) {
sections.add(section);
}

public Optional<Section> findSameUpStation(final Section section) {
public Section findSameUpStation(final Section section) {
return sections.stream()
.filter(section::isSameUpStation)
.findFirst();
.findFirst()
.orElse(null);
}

public Optional<Section> findSameDownStation(final Section section) {
public Section findSameDownStation(final Section section) {
return sections.stream()
.filter(section::isSameDownStation)
.findFirst();
.findFirst()
.orElse(null);
}

public int count() {
Expand All @@ -83,32 +87,39 @@ public Station findStationById(final long stationId) {

public void remove(final long stationId, Consumer<Section> syncLine) {
Station station = findStationById(stationId);
Optional<Section> upLineStation = findUpStation(station);
Optional<Section> downLineStation = findDownStation(station);
Section upLineStation = findUpStation(station);
Section downLineStation = findDownStation(station);

if (upLineStation.isPresent() && downLineStation.isPresent()) {
Station newUpStation = downLineStation.get().upStation();
Station newDownStation = upLineStation.get().downStation();
int newDistance = upLineStation.get().getDistance() + downLineStation.get().getDistance();
if (Objects.nonNull(upLineStation) && Objects.nonNull(downLineStation)) {
Station newUpStation = downLineStation.upStation();
Station newDownStation = upLineStation.downStation();
int newDistance = upLineStation.getDistance() + downLineStation.getDistance();
Section section = new Section(newUpStation, newDownStation, newDistance);
syncLine.accept(section);
sections.add(section);
}

upLineStation.ifPresent(sections::remove);
downLineStation.ifPresent(sections::remove);
if (Objects.nonNull(upLineStation)) {
sections.remove(upLineStation);
}

if (Objects.nonNull(downLineStation)) {
sections.remove(downLineStation);
}
}

private Optional<Section> findDownStation(final Station station) {
private Section findDownStation(final Station station) {
return sections.stream()
.filter(section -> section.isSameDownStation(station))
.findFirst();
.findFirst()
.orElse(null);
}

private Optional<Section> findUpStation(final Station station) {
private Section findUpStation(final Station station) {
return sections.stream()
.filter(section -> section.isSameUpStation(station))
.findFirst();
.findFirst()
.orElse(null);
}

public boolean isEmpty() {
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/nextstep/subway/path/application/PathService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package nextstep.subway.path.application;

import nextstep.subway.line.domain.LineRepository;
import nextstep.subway.path.domain.PathFinder;
import nextstep.subway.path.ui.PathResponse;
import nextstep.subway.station.domain.Station;
import nextstep.subway.station.domain.StationRepository;
import org.springframework.stereotype.Service;

@Service
public class PathService {

private final LineRepository lineRepository;
private final StationRepository stationRepository;

public PathService(final LineRepository lineRepository, final StationRepository stationRepository) {
this.lineRepository = lineRepository;
this.stationRepository = stationRepository;
}

public PathResponse findShortestPath(final Long sourceId, final Long targetId) {
Station sourceStation = findStationById(sourceId);
Station targetStation = findStationById(targetId);
PathFinder pathFinder = PathFinder.from(lineRepository.findAll());
return pathFinder.getShortestPath(sourceStation, targetStation);
}

private Station findStationById(final Long id) {
return stationRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException(id + "에 해당하는 Station을 찾을 수 없습니다."));
}
}
50 changes: 50 additions & 0 deletions src/main/java/nextstep/subway/path/domain/PathFinder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package nextstep.subway.path.domain;

import nextstep.subway.line.domain.Line;
import nextstep.subway.path.ui.PathResponse;
import nextstep.subway.station.domain.Station;
import nextstep.subway.station.dto.StationResponse;
import org.jgrapht.GraphPath;
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
import org.jgrapht.graph.DefaultWeightedEdge;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class PathFinder {
private final SubwayGraph graph;

private PathFinder(List<Line> lines) {
this.graph = new SubwayGraph(DefaultWeightedEdge.class, lines);
}

public static PathFinder from(final List<Line> lines) {
return new PathFinder(lines);
}

public PathResponse getShortestPath(final Station sourceStation, final Station targetStation) {
validate(sourceStation, targetStation);
GraphPath<Station, DefaultWeightedEdge> shortestPath =
new DijkstraShortestPath<>(graph).getPath(sourceStation, targetStation);
validate(shortestPath);
List<StationResponse> responses = shortestPath.getVertexList()
.stream()
.map(StationResponse::of)
.collect(Collectors.toList());

return new PathResponse(responses, (int) shortestPath.getWeight());
}

private void validate(final GraphPath<Station, DefaultWeightedEdge> shortestPath) {
if (Objects.isNull(shortestPath)) {
throw new IllegalArgumentException("출발역과 도착역이 연결이 되어 있지 않습니다.");
}
}

private void validate(final Station sourceStation, final Station targetStation) {
if (Objects.equals(sourceStation, targetStation)) {
throw new IllegalArgumentException("출발역과 도착역이 " + sourceStation.getName() + "으로 동일합니다.");
}
}
}
40 changes: 40 additions & 0 deletions src/main/java/nextstep/subway/path/domain/SubwayGraph.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package nextstep.subway.path.domain;

import nextstep.subway.line.domain.Line;
import nextstep.subway.line.domain.Section;
import nextstep.subway.station.domain.Station;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.jgrapht.graph.WeightedMultigraph;

import java.util.List;

public class SubwayGraph extends WeightedMultigraph<Station, DefaultWeightedEdge> {
private final List<Line> lines;

public SubwayGraph(final Class<? extends DefaultWeightedEdge> edgeClass, final List<Line> lines) {
super(edgeClass);
this.lines = lines;
initializeVertex();
initializeEdge();
}

private void initializeVertex() {
lines.stream()
.flatMap(sections -> sections.getStations().stream())
.forEach(this::addVertex);
}

private void initializeEdge() {
lines.stream()
.flatMap(sections -> sections.getSections().stream())
.forEach(this::setEdgeWeight);
}

private DefaultWeightedEdge addEdge(Section section) {
return addEdge(section.upStation(), section.downStation());
}

private void setEdgeWeight(final Section section) {
setEdgeWeight(addEdge(section), section.getDistance());
}
}

0 comments on commit bb35344

Please sign in to comment.