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
+ * 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