diff --git a/src/main/java/com/johngrib/objects/_02_movie/AmountDiscountPolicy.java b/src/main/java/com/johngrib/objects/_02_movie/AmountDiscountPolicy.java new file mode 100644 index 0000000..8a92672 --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/AmountDiscountPolicy.java @@ -0,0 +1,21 @@ +package com.johngrib.objects._02_movie; + +/** + * 금액 할인 정책. + *

+ * 조건을 만족할 경우 일정 금액을 할인해주는 정책. + */ +public class AmountDiscountPolicy extends DefaultDiscountPolicy { + /** 할인 요금. */ + private Money discountAmount; + + public AmountDiscountPolicy(Money discountAmount, DiscountCondition... conditions) { + super(conditions); + this.discountAmount = discountAmount; + } + + @Override + protected Money getDiscountAmount(Screening screening) { + return discountAmount; + } +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/Customer.java b/src/main/java/com/johngrib/objects/_02_movie/Customer.java new file mode 100644 index 0000000..926c3da --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/Customer.java @@ -0,0 +1,4 @@ +package com.johngrib.objects._02_movie; + +public class Customer { +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/DefaultDiscountPolicy.java b/src/main/java/com/johngrib/objects/_02_movie/DefaultDiscountPolicy.java new file mode 100644 index 0000000..394f6d2 --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/DefaultDiscountPolicy.java @@ -0,0 +1,35 @@ +package com.johngrib.objects._02_movie; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** 기본 할인 정책. */ +public abstract class DefaultDiscountPolicy implements DiscountPolicy { + /** 하나의 할인 정책은 여러 개의 할인 조건을 포함할 수 있다. */ + private List conditions = new ArrayList<>(); + + public DefaultDiscountPolicy(DiscountCondition... conditions) { + this.conditions = Arrays.asList(conditions); + } + + /** + * 할인 금액을 계산한다. + *

+ * Template Method. + * + * @param screening + * @return + */ + @Override + public Money calculateDiscountAmount(Screening screening) { + for (DiscountCondition each : conditions) { + if (each.isSatisfiedBy(screening)) { + return getDiscountAmount(screening); + } + } + return Money.ZERO; + } + + abstract protected Money getDiscountAmount(Screening screening); +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/DiscountCondition.java b/src/main/java/com/johngrib/objects/_02_movie/DiscountCondition.java new file mode 100644 index 0000000..5f854b9 --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/DiscountCondition.java @@ -0,0 +1,12 @@ +package com.johngrib.objects._02_movie; + +/** 할인 조건. */ +public interface DiscountCondition { + /** + * 전달된 상영 정보가 할인 조건을 만족시키면 true 를 리턴한다. + * + * @param screening 상영 정보 + * @return 할인 조건을 만족했다면 true + */ + boolean isSatisfiedBy(Screening screening); +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/DiscountPolicy.java b/src/main/java/com/johngrib/objects/_02_movie/DiscountPolicy.java new file mode 100644 index 0000000..93e5ce6 --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/DiscountPolicy.java @@ -0,0 +1,12 @@ +package com.johngrib.objects._02_movie; + +/** 할인 정책. */ +public interface DiscountPolicy { + /** + * 할인 금액을 계산한다. + * + * @param screening 상영 정보 + * @return 할인 금액 + */ + Money calculateDiscountAmount(Screening screening); +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/Money.java b/src/main/java/com/johngrib/objects/_02_movie/Money.java new file mode 100644 index 0000000..d702597 --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/Money.java @@ -0,0 +1,47 @@ +package com.johngrib.objects._02_movie; + +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.math.BigDecimal; + +/** 금액. */ +@EqualsAndHashCode +@ToString +public class Money { + public static final Money ZERO = Money.wons(0); + + private final BigDecimal amount; + + public static Money wons(long amount) { + return new Money(BigDecimal.valueOf(amount)); + } + + public static Money wons(double amount) { + return new Money(BigDecimal.valueOf(amount)); + } + + Money(BigDecimal amount) { + this.amount = amount; + } + + public Money plus(Money amount) { + return new Money(this.amount.add(amount.amount)); + } + + public Money minus(Money amount) { + return new Money(this.amount.subtract(amount.amount)); + } + + public Money times(double percent) { + return new Money(this.amount.multiply(BigDecimal.valueOf(percent))); + } + + public boolean isLessThan(Money other) { + return amount.compareTo(other.amount) < 0; + } + + public boolean isGreaterThanOrEqual(Money other) { + return amount.compareTo(other.amount) >= 0; + } +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/Movie.java b/src/main/java/com/johngrib/objects/_02_movie/Movie.java new file mode 100644 index 0000000..1698ddb --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/Movie.java @@ -0,0 +1,45 @@ +package com.johngrib.objects._02_movie; + +import lombok.Getter; + +import java.time.Duration; + +/** 영화. */ +public class Movie { + private String title; + private Duration runningTime; + /** 기본 요금. */ + @Getter + private Money fee; + /** 할인 정책. */ + private DiscountPolicy discountPolicy; + + public Movie(String title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) { + this.title = title; + this.runningTime = runningTime; + this.fee = fee; + this.discountPolicy = discountPolicy; + } + + /** + * 할인된 금액을 계산해 리턴한다. + * + * @param screening 상영 정보 + * @return 할인된 금액 + */ + public Money calculateMovieFee(Screening screening) { + if (discountPolicy == null) { + return fee; + } + return fee.minus(discountPolicy.calculateDiscountAmount(screening)); + } + + /** + * 실행 시점에 할인 정책을 변경한다. + * + * @param discountPolicy 변경할 할인 정책. + */ + public void changeDiscountPolicy(DiscountPolicy discountPolicy) { + this.discountPolicy = discountPolicy; + } +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/NoneDiscountPolicy.java b/src/main/java/com/johngrib/objects/_02_movie/NoneDiscountPolicy.java new file mode 100644 index 0000000..cb92892 --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/NoneDiscountPolicy.java @@ -0,0 +1,11 @@ +package com.johngrib.objects._02_movie; + +/** + * 할인하지 않는 할인 정책. + */ +public class NoneDiscountPolicy implements DiscountPolicy { + @Override + public Money calculateDiscountAmount(Screening screening) { + return Money.ZERO; + } +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/PercentDiscountPolicy.java b/src/main/java/com/johngrib/objects/_02_movie/PercentDiscountPolicy.java new file mode 100644 index 0000000..dabba41 --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/PercentDiscountPolicy.java @@ -0,0 +1,17 @@ +package com.johngrib.objects._02_movie; + +/** 일정 비율 할인 정책. */ +public class PercentDiscountPolicy extends DefaultDiscountPolicy { + /** 할인 비율. */ + private double percent; + + public PercentDiscountPolicy(double percent, DiscountCondition... conditions) { + super(conditions); + this.percent = percent; + } + + @Override + protected Money getDiscountAmount(Screening screening) { + return screening.getMovieFee().times(percent); + } +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/PeriodCondition.java b/src/main/java/com/johngrib/objects/_02_movie/PeriodCondition.java new file mode 100644 index 0000000..8be5d2a --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/PeriodCondition.java @@ -0,0 +1,31 @@ +package com.johngrib.objects._02_movie; + +import java.time.DayOfWeek; +import java.time.LocalTime; + +/** 상영 기간 기준 할인 조건. */ +public class PeriodCondition implements DiscountCondition { + /** 요일 */ + private DayOfWeek dayOfWeek; + private LocalTime startTime; + private LocalTime endTime; + + public PeriodCondition(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime) { + this.dayOfWeek = dayOfWeek; + this.startTime = startTime; + this.endTime = endTime; + } + + /** + * 상영 시작 시간이 기간 안에 포함된다면 true 를 리턴한다. + * + * @param screening 상영 정보 + * @return 할인 기준을 만족하면 true. + */ + @Override + public boolean isSatisfiedBy(Screening screening) { + return screening.getStartTime().getDayOfWeek().equals(dayOfWeek) + && startTime.compareTo(screening.getStartTime().toLocalTime()) <= 0 + && endTime.compareTo(screening.getStartTime().toLocalTime()) >= 0; + } +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/README.md b/src/main/java/com/johngrib/objects/_02_movie/README.md new file mode 100644 index 0000000..36d9a1d --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/README.md @@ -0,0 +1,3 @@ +# 02. 객체지향 프로그래밍 + +영화 예매 시스템 diff --git a/src/main/java/com/johngrib/objects/_02_movie/Reservation.java b/src/main/java/com/johngrib/objects/_02_movie/Reservation.java new file mode 100644 index 0000000..3477261 --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/Reservation.java @@ -0,0 +1,20 @@ +package com.johngrib.objects._02_movie; + +/** 예매 정보. */ +public class Reservation { + /** 고객. */ + private Customer customer; + /** 상영 정보. */ + private Screening screening; + /** 예매 요금. */ + private Money fee; + /** 인원 수. */ + private int audienceCount; + + public Reservation(Customer customer, Screening screening, Money fee, int audienceCount) { + this.customer = customer; + this.screening = screening; + this.fee = fee; + this.audienceCount = audienceCount; + } +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/Screening.java b/src/main/java/com/johngrib/objects/_02_movie/Screening.java new file mode 100644 index 0000000..f1189da --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/Screening.java @@ -0,0 +1,56 @@ +package com.johngrib.objects._02_movie; + +import java.time.LocalDateTime; + +/** 상영. 상영은 사용자들이 예매하는 대상이다. */ +public class Screening { + /** 상영할 영화. */ + private Movie movie; + /** 상영 순번. */ + private int sequence; + /** 상영 시작 시간. */ + private LocalDateTime whenScreened; + + public Screening(Movie movie, int sequence, LocalDateTime whenScreened) { + this.movie = movie; + this.sequence = sequence; + this.whenScreened = whenScreened; + } + + /** 상영 시작 시간을 리턴한다. */ + public LocalDateTime getStartTime() { + return whenScreened; + } + + /** 순번이 일치하면 true 를 리턴한다. */ + public boolean isSequence(int sequence) { + return this.sequence == sequence; + } + + /** 기본 요금을 리턴한다. */ + public Money getMovieFee() { + return movie.getFee(); + } + + /** + * 영화를 예매하고, 예매 정보를 리턴한다. + * + * @param customer 예매자 + * @param audienceCount 예매 인원 수 + * @return 예매 정보 + */ + public Reservation reserve(Customer customer, int audienceCount) { + return new Reservation(customer, this, calculateFee(audienceCount), audienceCount); + } + + /** + * 전체 예매 요금을 계산해 리턴한다. + * + * @param audienceCount 예매 인원 수 + * @return 예매 요금 + */ + private Money calculateFee(int audienceCount) { + return movie.calculateMovieFee(this) + .times(audienceCount); + } +} diff --git a/src/main/java/com/johngrib/objects/_02_movie/SequenceCondition.java b/src/main/java/com/johngrib/objects/_02_movie/SequenceCondition.java new file mode 100644 index 0000000..01bc38d --- /dev/null +++ b/src/main/java/com/johngrib/objects/_02_movie/SequenceCondition.java @@ -0,0 +1,16 @@ +package com.johngrib.objects._02_movie; + +/** 순번 기준 할인 조건. */ +public class SequenceCondition implements DiscountCondition { + /** 순번. */ + private int sequence; + + public SequenceCondition(int sequence) { + this.sequence = sequence; + } + + @Override + public boolean isSatisfiedBy(Screening screening) { + return screening.isSequence(sequence); + } +} diff --git a/src/test/java/com/johngrib/objects/_02_movie/MovieTest.java b/src/test/java/com/johngrib/objects/_02_movie/MovieTest.java new file mode 100644 index 0000000..9d28652 --- /dev/null +++ b/src/test/java/com/johngrib/objects/_02_movie/MovieTest.java @@ -0,0 +1,393 @@ +package com.johngrib.objects._02_movie; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.*; +import java.util.List; +import java.util.stream.Collectors; + +@SuppressWarnings({"InnerClassMayBeStatic", "NonAsciiCharacters"}) +@DisplayName("Movie 클래스") +class MovieTest { + private final Money given_고정할인금액 = Money.wons(800); + private final double given_할인비율 = 0.1; + private final LocalDateTime given_월요일 = LocalDate.of(2020, Month.MARCH, 2).atStartOfDay(); + private final LocalDateTime given_화요일 = given_월요일.plusDays(1); + private final LocalDateTime given_목요일 = given_월요일.plusDays(3); + private final LocalDateTime given_일요일 = given_월요일.plusDays(6); + private final LocalDateTime given_일요일_오후 = given_일요일.withHour(13).withMinute(30); + + Movie given_아바타() { + return new Movie( + "아바타", + Duration.ofMinutes(120), + Money.wons(10000), + given_아바타_할인정책); + } + + private final DiscountPolicy given_아바타_할인정책 = new AmountDiscountPolicy( + given_고정할인금액, + new SequenceCondition(1), + new SequenceCondition(10), + new PeriodCondition(DayOfWeek.MONDAY, LocalTime.of(10, 0), LocalTime.of(11, 59)), + new PeriodCondition(DayOfWeek.THURSDAY, LocalTime.of(10, 0), LocalTime.of(20, 59))); + + Movie given_타이타닉() { + return new Movie( + "타이타닉", + Duration.ofMinutes(180), + Money.wons(11000), + given_타이타닉_할인정책); + } + + private final DiscountPolicy given_타이타닉_할인정책 = new PercentDiscountPolicy(0.1, + new PeriodCondition(DayOfWeek.TUESDAY, LocalTime.of(14, 0), LocalTime.of(16, 59)), + new SequenceCondition(2), + new PeriodCondition(DayOfWeek.THURSDAY, LocalTime.of(10, 0), LocalTime.of(13, 59))); + + Movie given_스타워즈() { + return new Movie( + "스타워즈", + Duration.ofMinutes(210), + Money.wons(10000), + given_스타워즈_할인정책); + } + + private final DiscountPolicy given_스타워즈_할인정책 = new NoneDiscountPolicy(); + + abstract class TestCalculateMovieFee { + abstract Movie givenMovie(); + + Money 기본_요금() { + return givenMovie().getFee(); + } + + Money subject(Screening screening) { + return givenMovie().calculateMovieFee(screening); + } + } + + @Nested + @DisplayName("calculateMovieFee 메소드는") + class Describe_calculateMovieFee { + @Nested + @DisplayName("주어진 영화가 '아바타'일때 (할인 조건: 상영 시작 시간, 상영 순번 / 할인 금액: 고정 금액)") + class Context_with_avatar extends TestCalculateMovieFee { + Movie givenMovie() { + return given_아바타(); + } + + @Nested + @DisplayName("상영 시작 시간이 할인 조건에 맞는다면") + class Context_with_valid_period { + final List 할인_조건에_맞는_상영_시작_시간들 = List.of( + // edge cases - 월요일 + given_월요일.withHour(10).withMinute(0), + given_월요일.withHour(11).withMinute(59), + // inner cases - 월요일 + given_월요일.withHour(10).withMinute(1), + given_월요일.withHour(11).withMinute(58), + // edge cases - 목요일 + given_목요일.withHour(10).withMinute(0), + given_목요일.withHour(20).withMinute(59), + // inner cases - 목요일 + given_목요일.withHour(10).withMinute(1), + given_목요일.withHour(11).withMinute(58) + ); + + List givenScreens() { + return 할인_조건에_맞는_상영_시작_시간들.stream() + .map(상영시간 -> new Screening(givenMovie(), 0, 상영시간)) + .collect(Collectors.toList()); + } + + @Test + @DisplayName("고정할인 금액만큼 할인된 금액을 리턴한다") + void it_returns_discounted_fee() { + for (Screening 할인되는_시간에_시작하는_상영 : givenScreens()) { + final Money 계산된_요금 = subject(할인되는_시간에_시작하는_상영); + + Assertions.assertEquals(기본_요금().minus(given_고정할인금액), 계산된_요금); + } + } + } + + @Nested + @DisplayName("상영 시작 시간이 할인 조건에 맞지 않는다면") + class Context_with_invalid_period { + final List 할인_조건에_맞지_않는_상영_시작_시간들 = List.of( + // 월요일 + given_월요일.withHour(9).withMinute(59), + given_월요일.withHour(12).withMinute(0), + // 목요일 + given_목요일.withHour(9).withMinute(59), + given_목요일.withHour(21).withMinute(0), + // 그 외의 요일 + given_화요일.withHour(10).withMinute(0), + given_화요일.withHour(10).withMinute(1), + given_화요일.withHour(10).withMinute(30) + ); + + List givenScreens() { + return 할인_조건에_맞지_않는_상영_시작_시간들.stream() + .map(상영시간 -> new Screening(givenMovie(), -1, 상영시간)) + .collect(Collectors.toList()); + } + + @Test + @DisplayName("할인되지 않은 금액을 리턴한다") + void it_returns_fee_does_not_discounted() { + for (Screening 할인되는_시간에_시작하는_상영 : givenScreens()) { + final Money 계산된_요금 = subject(할인되는_시간에_시작하는_상영); + + Assertions.assertEquals(기본_요금(), 계산된_요금); + } + } + } + + @Nested + @DisplayName("상영 순번이 할인 조건에 맞는다면") + class Context_with_valid_seq { + final List 지정된_상영_순번 = List.of(1, 10); + + List givenScreens() { + return 지정된_상영_순번.stream() + .map(seq -> new Screening(givenMovie(), seq, given_일요일_오후)) + .collect(Collectors.toList()); + } + + @Test + @DisplayName("고정할인 금액만큼 할인된 금액을 리턴한다") + void it_returns_discounted_fee() { + for (Screening 할인되는_순번의_상영 : givenScreens()) { + final Money 계산된_요금 = subject(할인되는_순번의_상영); + + Assertions.assertEquals(기본_요금().minus(given_고정할인금액), 계산된_요금); + } + } + } + + @Nested + @DisplayName("상영 순번이 할인 조건에 맞지 않는다면") + class Context_with_invalid_seq { + final List 지정되지_않은_상영_순번 = List.of(2, 9); + + List givenScreens() { + return 지정되지_않은_상영_순번.stream() + .map(seq -> new Screening(givenMovie(), seq, given_일요일_오후)) + .collect(Collectors.toList()); + } + + @Test + @DisplayName("할인되지 않은 금액을 리턴한다") + void it_returns_fee_not_discounted() { + for (Screening 상영 : givenScreens()) { + final Money 계산된_요금 = subject(상영); + + Assertions.assertEquals(기본_요금(), 계산된_요금); + } + } + } + } + + @Nested + @DisplayName("주어진 영화가 '타이타닉'일때 (할인 조건: 상영 시작 시간, 상영 순번 / 할인 금액: 퍼센트)") + class Context_with_titanic extends TestCalculateMovieFee { + Movie givenMovie() { + return given_타이타닉(); + } + + @Nested + @DisplayName("상영 시작 시간이 할인 조건에 맞는다면") + class Context_with_valid_period { + final List 지정된_기간_내의_시간들 = List.of( + // edge cases - 화요일 + given_화요일.withHour(14).withMinute(0), + given_화요일.withHour(16).withMinute(59), + // inner cases - 화요일 + given_화요일.withHour(14).withMinute(1), + given_화요일.withHour(16).withMinute(58), + // edge cases - 목요일 + given_목요일.withHour(10).withMinute(0), + given_목요일.withHour(13).withMinute(59), + // inner cases - 목요일 + given_목요일.withHour(10).withMinute(1), + given_목요일.withHour(13).withMinute(58) + ); + + List givenScreens() { + return 지정된_기간_내의_시간들.stream() + .map(상영시간 -> new Screening(givenMovie(), 0, 상영시간)) + .collect(Collectors.toList()); + } + + @Test + @DisplayName("지정된 비율만큼 할인된 금액을 리턴한다") + void it_returns_discounted_fee() { + for (Screening 할인되는_시간에_시작하는_상영 : givenScreens()) { + final Money 계산된_요금 = subject(할인되는_시간에_시작하는_상영); + + Assertions.assertEquals(기본_요금().times(1 - given_할인비율), 계산된_요금); + } + } + } + + @Nested + @DisplayName("상영 시작 시간이 할인 조건에 맞지 않는다면") + class Context_with_invalid_period { + final List 할인_조건에_맞지_않는_상영_시작_시간들 = List.of( + // 월요일 + given_월요일.withHour(9).withMinute(59), + given_월요일.withHour(12).withMinute(0), + // 목요일 + given_목요일.withHour(9).withMinute(59), + given_목요일.withHour(21).withMinute(0), + // 그 외의 요일 + given_화요일.withHour(10).withMinute(0), + given_화요일.withHour(10).withMinute(1), + given_화요일.withHour(10).withMinute(30) + ); + + List givenScreens() { + return 할인_조건에_맞지_않는_상영_시작_시간들.stream() + .map(상영시간 -> new Screening(givenMovie(), -1, 상영시간)) + .collect(Collectors.toList()); + } + + @Test + @DisplayName("할인되지 않은 금액을 리턴한다") + void it_returns_fee_not_discounted() { + for (Screening 할인되는_시간에_시작하는_상영 : givenScreens()) { + final Money 계산된_요금 = subject(할인되는_시간에_시작하는_상영); + + Assertions.assertEquals(기본_요금(), 계산된_요금); + } + } + } + + @Nested + @DisplayName("상영 순번이 할인 조건에 맞는다면") + class Context_with_valid_seq { + final List 지정된_상영_순번 = List.of(2); + + List givenScreens() { + return 지정된_상영_순번.stream() + .map(seq -> new Screening(givenMovie(), seq, given_일요일_오후)) + .collect(Collectors.toList()); + } + + @Test + @DisplayName("지정된 비율만큼 할인된 금액을 리턴한다") + void it_returns_discounted_fee() { + for (Screening 할인되는_순번의_상영 : givenScreens()) { + final Money 계산된_요금 = subject(할인되는_순번의_상영); + + Assertions.assertEquals(기본_요금().times(1 - given_할인비율), 계산된_요금); + } + } + } + + @Nested + @DisplayName("상영 순번이 할인 조건에 맞지 않는다면") + class Context_with_invalid_seq { + final List 지정되지_않은_상영_순번 = List.of(1, 3, 4, 5, 6, 7, 8, 9, 10); + + List givenScreens() { + return 지정되지_않은_상영_순번.stream() + .map(seq -> new Screening(givenMovie(), seq, given_일요일_오후)) + .collect(Collectors.toList()); + } + + @Test + @DisplayName("할인되지 않은 금액을 리턴한다") + void it_returns_fee_not_discounted() { + for (Screening 상영 : givenScreens()) { + final Money 계산된_요금 = subject(상영); + + Assertions.assertEquals(기본_요금(), 계산된_요금); + } + } + } + } + + @Nested + @DisplayName("주어진 영화가 '스타워즈'일때 (할인 조건 없음)") + class Context_with_starwars extends TestCalculateMovieFee { + Movie givenMovie() { + return given_스타워즈(); + } + + @Test + @DisplayName("할인되지 않은 금액을 리턴한다") + void it_returns_fee_not_discounted() { + final Screening 상영 = new Screening(givenMovie(), 0, given_일요일_오후); + final Money 계산된_요금 = subject(상영); + + Assertions.assertEquals(기본_요금(), 계산된_요금, "할인되지 않았으므로 금액은 변함이 없다"); + } + } + } + + @Nested + @DisplayName("changeDiscountPolicy 메소드는") + class Describe_changeDiscountPolicy { + @Nested + @DisplayName("주어진 영화가 '스타워즈'일때 (할인 조건 없음)") + class Context_with_starwars { + Movie givenMovie() { + return given_스타워즈(); + } + + @Nested + @DisplayName("'아바타'의 할인정책이 주어지면") + class Context_with_avatar_discount_policy { + final DiscountPolicy given_할인정책 = given_아바타_할인정책; + + @Test + @DisplayName("주어진 할인 정책으로 할인정책을 교체하고 void를 리턴한다") + void it_changes_the_discount_policy() { + final Movie 스타워즈 = givenMovie(); + final Money 기본_요금 = 스타워즈.getFee(); + 스타워즈.changeDiscountPolicy(given_할인정책); + + { + /* 변경된 할인 정책으로 할인이 되는지 확인한다. */ + final int 아바타_할인_조건_순번 = 1; + final Screening 상영 = new Screening(스타워즈, 아바타_할인_조건_순번, given_일요일_오후); + final Money 계산된_요금 = 스타워즈.calculateMovieFee(상영); + + Assertions.assertEquals(기본_요금.minus(given_고정할인금액), 계산된_요금, + "할인되지 않는 스타워즈의 요금이 아바타의 정책으로 할인된다"); + } + } + } + + @Nested + @DisplayName("'타이타닉'의 할인정책이 주어지면") + class Context_with_starwars_policy { + final DiscountPolicy given_할인정책 = given_타이타닉_할인정책; + + @Test + @DisplayName("주어진 할인 정책으로 할인정책을 교체하고 void를 리턴한다") + void it_changes_the_discount_policy() { + final Movie movie = givenMovie(); + final Money 기본_요금 = movie.getFee(); + movie.changeDiscountPolicy(given_할인정책); + + { + /* 변경된 할인 정책으로 할인이 되는지 확인한다. */ + final int 타이타닉_할인_조건_순번 = 2; + final Screening 상영 = new Screening(movie, 타이타닉_할인_조건_순번, given_일요일_오후); + final Money 계산된_요금 = movie.calculateMovieFee(상영); + + Assertions.assertEquals(기본_요금.times(1 - given_할인비율), 계산된_요금, + "할인되지 않는 스타워즈의 요금이 타이타닉의 정책으로 할인된다"); + } + } + } + } + } +} \ No newline at end of file