Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3단계 - 요금 정책 추가] 우르(김현우) 미션 제출합니다 #181

Merged
merged 41 commits into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
62a03e6
fix : 제일 앞의 Section에 역을 추가할 경우 예외 수정
java-saeng May 18, 2023
752ed20
refactor : 도메인이 외부 라이브러리에 바로 의존하지 않고 인터페이스에 의존하면서 최단 경로 조회
java-saeng May 19, 2023
6e9cf73
feat : 하나의 yml 안에 여러 profile 설정해보기
java-saeng May 19, 2023
f9eebfc
feat : 최소 경로 구할 때, Section 반환하는 기능 추가
java-saeng May 20, 2023
8eb7638
feat : 호선에 따라서 요금 정책 기능 추가
java-saeng May 20, 2023
8856369
refactor : SubwayPricePolicy -> SubwayChargePolicy 로 변경
java-saeng May 20, 2023
e8ec926
feat : 연령별 요금 할인 정책 기능 구현
java-saeng May 20, 2023
dac9a3b
refactor : 돈 관련 메서드 모두 Money로 변경
java-saeng May 20, 2023
402a425
refactor : 요금 할인, 요금 부과 계산을 ChargePolicyComposite 에서 모두 하기
java-saeng May 21, 2023
c2f7753
refactor : 패키지 수정
java-saeng May 21, 2023
6eddf3d
refactor : 예외 메시지 전달
java-saeng May 21, 2023
a42f94a
refactor : 불필요한 메서드 삭제
java-saeng May 21, 2023
aa0e619
feat : line id 로 조회하는 API 생성
java-saeng May 21, 2023
781c569
refactor : line 조회 시 id 반환
java-saeng May 21, 2023
71c6785
refactor : 도메인 패키지 변경에 따른 테스트 코드 패키지 변경
java-saeng May 21, 2023
4bcca05
refactor : 도메인 패키지 변경에 따른 테스트 코드 패키지 변경
java-saeng May 21, 2023
13d1d2d
test : LineDao 테스트 작성
java-saeng May 21, 2023
19f4307
test : SectionDao 테스트 작성
java-saeng May 21, 2023
074f896
test : LineControllerTest 작성
java-saeng May 21, 2023
35ea991
test : RouteControllerTest 작성
java-saeng May 21, 2023
ff4297e
test : StationController 작성
java-saeng May 21, 2023
9c12f91
test : LineCommandService Integration Test 추가
java-saeng May 21, 2023
e90d594
test : LineQueryService Integration Test 추가
java-saeng May 21, 2023
028a82a
test : RouteQueryService Integration Test 추가
java-saeng May 21, 2023
3927e12
test : SectionCommandService Integration Test 추가
java-saeng May 22, 2023
7c02029
test : SectionQueryService Integration Test 추가
java-saeng May 22, 2023
868b698
test : StationService Integration Test 추가
java-saeng May 22, 2023
f385149
refactor : exception 패키지 이동
java-saeng May 22, 2023
5f0a505
refactor : java style google 로 변경
java-saeng May 23, 2023
89a3d4a
refactor : 메서드 명 변경
java-saeng May 23, 2023
7a95acc
refactor : 경로 조회 시 하나의 service 에서 조회하도록 변경
java-saeng May 23, 2023
beded7a
refactor : DefaultFarePolicy -> DistanceFarePolicy 로 이름 변경
java-saeng May 23, 2023
d320f85
refactor : Route 도메인 삭제
java-saeng May 25, 2023
c9ee774
feat : 처음 애플리케이션이 실행될 때와 역에 변경이 일어날 경우 다익스트라 그래프 수정하는 이벤트 추가
java-saeng May 25, 2023
62caf14
test : yml 테스트 시 애플리케이션 컨테이네가 띄워지면 자동으로 이벤트가 수행되기 때문에 이를 방지하고자 Profil…
java-saeng May 25, 2023
d3d66cb
refactor : Policy 관련 빈 해제하고, Composite 만 빈 등록
java-saeng May 25, 2023
10b95bb
refactor : Line 생성 시 이벤트 발행
java-saeng May 25, 2023
966df84
refactor : 최소 경로 구할 때, key 값을 역 이름(String)이 아닌 Station으로 사용
java-saeng May 25, 2023
c6984bb
refactor : Event 삭제
java-saeng May 25, 2023
1390aaf
refactor : 그래프 생성하는 Factory 클래스 생성
java-saeng May 25, 2023
727c10b
refactor : 패키지 구조 변경
java-saeng May 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/main/java/subway/controller/LineController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import subway.domain.Line;
import subway.domain.line.Line;
import subway.service.LineCommandService;
import subway.service.LineQueryService;
import subway.service.dto.LineResponse;
Expand All @@ -30,22 +31,29 @@ public LineController(final LineCommandService lineCommandService, final LineQue

@GetMapping("/lines")
@ResponseStatus(HttpStatus.OK)
public List<LineResponse> showLines(@RequestParam(value = "lineName", required = false) String lineName) {
public List<LineResponse> showLine(@RequestParam(value = "lineName", required = false) String lineName) {
final List<Line> lines = lineQueryService.searchLines(lineName);

return lines.stream()
.map(line -> new LineResponse(line.getName(), mapToSectionResponseFrom(line)))
.map(line -> new LineResponse(line.getName(), mapToSectionResponseFrom(line), line.getId()))
.collect(Collectors.toList());
}

@GetMapping("/lines/{line-id}")

Choose a reason for hiding this comment

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

Suggested change
@GetMapping("/lines/{line-id}")
@GetMapping("/lines/{lineId}")

보통은 카멜케이스를 쓰는것 같아요

Copy link
Author

Choose a reason for hiding this comment

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

https://restfulapi.net/resource-naming/

저는 해당 문서가 restful 공식문서인지 아닌지는 모르겠지만, best-practice를 보면 {id} 또는 {resource-id} 로 사용하고 있어서 이게 표준인 줄 알았습니다,,

물론 표준을 무조건 지켜야하는건 아니지만, 공식문서에서는 - 를 사용하던데, 이 부분은 같은 팀간의 컨벤션으로 결정하는 걸까요?

Choose a reason for hiding this comment

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

아하 이런것도 공식문서가 있네요
몰랐어요!
공식문서를 따르는게 좋을것 같아요

public LineResponse showLine(@PathVariable("line-id") Long lineId) {
final Line line = lineQueryService.searchByLineId(lineId);

return new LineResponse(line.getName(), mapToSectionResponseFrom(line), line.getId());
}

@PostMapping("/lines")
@ResponseStatus(HttpStatus.CREATED)
public LineResponse registerLine(@RequestBody RegisterLineRequest registerLineRequest) {
lineCommandService.registerLine(registerLineRequest);

final Line line = lineQueryService.searchByLineName(registerLineRequest.getLineName());

return new LineResponse(line.getName(), mapToSectionResponseFrom(line));
return new LineResponse(line.getName(), mapToSectionResponseFrom(line), line.getId());
}

private List<SectionResponse> mapToSectionResponseFrom(final Line line) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/subway/controller/StationController.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public StationRegisterResponse registerStation(@RequestBody StationRegisterReque
}

@DeleteMapping("/stations")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteStation(@RequestBody StationDeleteRequest stationDeleteRequest) {
stationService.deleteStation(stationDeleteRequest);
}
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/subway/dao/LineDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ public List<LineEntity> findAll() {
return jdbcTemplate.query(sql, rowMapper);
}

public Optional<LineEntity> findByLineId(final Long lineId) {
final String sql = "SELECT * FROM LINE L WHERE L.id = ?";

try {
return Optional.ofNullable(jdbcTemplate.queryForObject(sql, rowMapper, lineId));
} catch (EmptyResultDataAccessException exception) {
return Optional.empty();
}
}

public void deleteById(final Long lineId) {
final String sql = "DELETE FROM LINE L WHERE L.id = ?";

Expand Down
66 changes: 66 additions & 0 deletions src/main/java/subway/domain/Money.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package subway.domain;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;

public class Money {

public static final Money ZERO = new Money(BigDecimal.ZERO);

private final BigDecimal value;

private Money(final BigDecimal value) {
validateNegative(value);
this.value = value;
}

public Money(final double value) {
this(BigDecimal.valueOf(value));
}

private void validateNegative(final BigDecimal value) {
if (value.doubleValue() < 0) {
throw new IllegalArgumentException("돈은 음수가 될 수 없습니다.");
}
}

public Money minus(final int value) {
return new Money(this.value.subtract(BigDecimal.valueOf(value)));
}

public Money calculateDiscountedPrice(final int percentage) {
final int discountedPercentage = 100 - percentage;
return new Money(this.value.multiply(BigDecimal.valueOf(discountedPercentage))
.divide(BigDecimal.valueOf(100), RoundingMode.HALF_DOWN));
}

public Money add(final BigDecimal value) {
return new Money(this.value.add(value));
}

public Money add(final Money money) {
return new Money(this.value.add(money.value));
}

public Money max(final Money other) {
return new Money(this.value.max(other.value));
}

public double getValue() {
return value.doubleValue();
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Money money = (Money) o;
return Objects.equals(value, money.value);
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}
74 changes: 0 additions & 74 deletions src/main/java/subway/domain/Route.java

This file was deleted.

6 changes: 0 additions & 6 deletions src/main/java/subway/domain/SubwayPricePolicy.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
package subway.domain;
package subway.domain.line;

import subway.domain.section.Section;
import subway.domain.station.Station;

import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -54,12 +57,23 @@ public void add(Section newSection) {
}

final Section targetSection = starter.findPreSectionOnAdd(newSection);
final Section sameStartSection = starter.findSameSectionOnAdd(newSection);

if (canExchangeStarter(targetSection)) {

if (starter.isLinked(newSection)) {
exchangeStarterOnAdd(newSection);
return;
}

if (sameStartSection != null) {
newSection.updateNextSection(starter);
starter.updateSectionOnAdd(newSection);
starter = newSection;
return;
}


throw new IllegalArgumentException("해당 섹션은 현재 Line에 추가할 수 없습니다.");
}

Expand Down
42 changes: 42 additions & 0 deletions src/main/java/subway/domain/policy/ChargePolicyComposite.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package subway.domain.policy;

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import subway.domain.Money;
import subway.domain.policy.discount.DiscountCondition;
import subway.domain.policy.discount.SubwayDiscountPolicy;
import subway.domain.policy.fare.SubwayFarePolicy;
import subway.domain.route.Route;

import java.util.List;

@Primary
@Component
public class ChargePolicyComposite implements SubwayFarePolicy, SubwayDiscountPolicy {

Choose a reason for hiding this comment

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

ChargePolicyComposite이 왜 SubwayFarePolicy, SubwayDiscountPolicy를 상속하는지 모르겠어요ㅠ

Copy link
Author

Choose a reason for hiding this comment

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

제가 원하던 설계 방식은 요금 방식이라는 객체에서 모든 요금들(요금 할인, 요금 부과)을 계산하고 나서의 값을 반환해주고 싶었습니다.

그런데 요금 부과와 요금 할인은 서로 필요한 조건들이 다르기 때문에 같이 추상화시키는데 어려움이 있었습니다.

그래서 제가 내린 결론은 요금 방식(ChargePolicyComposite)요금 할인(SubwayDiscountPolicy), 요금 부과(SubwayFarePolicy)를 구현해서 요금 방식에서 calculatediscount를 통해 모든 요금을 계산하고나서의 결과값을 반환해주도록 했습니다.

많이 어색한걸까요,,?


private final List<SubwayFarePolicy> farePolicies;
private final List<SubwayDiscountPolicy> discountPolicies;

public ChargePolicyComposite(
final List<SubwayFarePolicy> farePolicies,
final List<SubwayDiscountPolicy> discountPolicies
) {
this.farePolicies = farePolicies;
this.discountPolicies = discountPolicies;
}

@Override
public Money calculate(final Route route) {
return farePolicies.stream()
.map(it -> it.calculate(route))
.reduce(Money.ZERO, Money::add);
}
choijy1705 marked this conversation as resolved.
Show resolved Hide resolved

@Override
public Money discount(final DiscountCondition discountCondition, final Money price) {
return discountPolicies.stream()
.reduce(price, (money, subwayDiscountPolicy) ->
subwayDiscountPolicy.discount(discountCondition, money),
(money1, money2) -> money2);
}
}
48 changes: 48 additions & 0 deletions src/main/java/subway/domain/policy/discount/AgeDiscountPolicy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package subway.domain.policy.discount;

import org.springframework.stereotype.Component;
import subway.domain.Money;

import java.util.EnumMap;
import java.util.Map;

@Component
public class AgeDiscountPolicy implements SubwayDiscountPolicy {

private static final Map<AgeGroup, DiscountValue> policyMap = new EnumMap<>(AgeGroup.class);

static {
policyMap.put(AgeGroup.CHILD, new DiscountValue(350, 50));
policyMap.put(AgeGroup.TEENAGER, new DiscountValue(350, 20));
policyMap.put(AgeGroup.NONE, new DiscountValue(0, 0));
}

@Override
public Money discount(final DiscountCondition discountCondition, final Money price) {

final AgeGroup ageGroup = AgeGroup.findAgeGroup(discountCondition.getAge());
final DiscountValue discountValue = policyMap.get(ageGroup);

return price.minus(discountValue.getDiscountPrice())
.calculateDiscountedPrice(discountValue.getPercent());
}

private static class DiscountValue {

private final int discountPrice;
private final int percent;

public DiscountValue(final int discountPrice, final int percent) {
this.discountPrice = discountPrice;
this.percent = percent;
}

public int getDiscountPrice() {
return discountPrice;
}

public int getPercent() {
return percent;
}
}
}
27 changes: 27 additions & 0 deletions src/main/java/subway/domain/policy/discount/AgeGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package subway.domain.policy.discount;

import java.util.Arrays;
import java.util.Objects;

public enum AgeGroup {

TEENAGER(13, 19),
CHILD(6, 13),
NONE(0, 0);

private final int ageLowLimit;
private final int ageHighLimit;

AgeGroup(final int ageLowLimit, final int ageHighLimit) {
this.ageLowLimit = ageLowLimit;
this.ageHighLimit = ageHighLimit;
}

public static AgeGroup findAgeGroup(final Integer target) {
return Arrays.stream(values())
.filter(age -> Objects.nonNull(target))

Choose a reason for hiding this comment

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

Integer 대신 int를 사용하면 굳이 null 체크 안해도 되겠네요!

Copy link
Author

Choose a reason for hiding this comment

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

이 부분은 일부러 null을 사용했습니다 !!

요금 계산할 때 나이에 따른 요금 할인 정책이 존재하는데, 나이를 입력하지 않는 경우에는 아무 할인도 적용되지 않게 처리하기 위해서 null 을 사용했습니다 !

만약 nullable하지 않게 하려면 ShortestRouteRequest 여기에서 age를 int로 받아와서 0으로 해야하는데 이렇게 처리하는게 나을까요???

Choose a reason for hiding this comment

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

의도한거라면 상관없을것 같네요!

.filter(age -> target >= age.ageLowLimit && target < age.ageHighLimit)
.findAny()
.orElse(NONE);
}
}
14 changes: 14 additions & 0 deletions src/main/java/subway/domain/policy/discount/DiscountCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package subway.domain.policy.discount;

public class DiscountCondition {

Choose a reason for hiding this comment

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

딱히 기능도 없고
복잡성만 늘리는 원시값 포장이지 않을까요?😭

Copy link
Author

Choose a reason for hiding this comment

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

나중에 할인 조건이 생기지 않을까 하는 생각에 조건을 가지는 객체를 생성해두었습니다,,

현재는 나이 밖에 없어서 파라미터에 age가 들어가지만, 나중에 다른 조건들이 필요할 때 age 를 파라미터로 사용한 관련 코드를 다 바꿔야 할 것 같아서 미리 만들어두었습니다.

조금 과한 경향이 있는걸까요?

Choose a reason for hiding this comment

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

개인적으로 필요한 시점에 추가하는게 가장 좋은것 같아요!


private final Integer age;

public DiscountCondition(final Integer age) {
this.age = age;
}

public Integer getAge() {
return age;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package subway.domain.policy.discount;

import subway.domain.Money;

public interface SubwayDiscountPolicy {

Money discount(final DiscountCondition discountCondition, final Money price);
}
Loading