From 68b2d2734f880e435340c91c3689c0eaeb8775cc Mon Sep 17 00:00:00 2001 From: doongu Date: Fri, 14 Jul 2023 20:26:34 +0900 Subject: [PATCH] =?UTF-8?q?feat=20-=20Chapter2=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EC=99=84=EB=A3=8C#10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ch2/doongu/ver1/AmountDiscountPolicy.java | 16 +++++ src/ch2/doongu/ver1/CallExample.java | 24 ++++++++ src/ch2/doongu/ver1/DiscountCondition.java | 10 ++++ src/ch2/doongu/ver1/DiscountPolicy.java | 38 ++++++++++++ src/ch2/doongu/ver1/Money.java | 42 ++++++++++++++ src/ch2/doongu/ver1/Movie.java | 58 +++++++++++++++++++ src/ch2/doongu/ver1/NoneDiscountPolicy.java | 21 +++++++ .../doongu/ver1/PercentDiscountPolicy.java | 15 +++++ src/ch2/doongu/ver1/PeriodCondition.java | 25 ++++++++ src/ch2/doongu/ver1/Reservation.java | 15 +++++ src/ch2/doongu/ver1/Screening.java | 36 ++++++++++++ src/ch2/doongu/ver1/SequenceCondition.java | 15 +++++ src/ch2/doongu/ver2/AmountDiscountPolicy.java | 16 +++++ src/ch2/doongu/ver2/CallExample.java | 24 ++++++++ .../doongu/ver2/DefaultDiscountPolicy.java | 34 +++++++++++ src/ch2/doongu/ver2/DiscountCondition.java | 10 ++++ src/ch2/doongu/ver2/DiscountPolicy.java | 16 +++++ src/ch2/doongu/ver2/Money.java | 42 ++++++++++++++ src/ch2/doongu/ver2/Movie.java | 58 +++++++++++++++++++ src/ch2/doongu/ver2/NoneDiscountPolicy.java | 8 +++ .../doongu/ver2/PercentDiscountPolicy.java | 15 +++++ src/ch2/doongu/ver2/PeriodCondition.java | 25 ++++++++ src/ch2/doongu/ver2/Reservation.java | 15 +++++ src/ch2/doongu/ver2/Screening.java | 36 ++++++++++++ src/ch2/doongu/ver2/SequenceCondition.java | 15 +++++ 25 files changed, 629 insertions(+) create mode 100644 src/ch2/doongu/ver1/AmountDiscountPolicy.java create mode 100644 src/ch2/doongu/ver1/CallExample.java create mode 100644 src/ch2/doongu/ver1/DiscountCondition.java create mode 100644 src/ch2/doongu/ver1/DiscountPolicy.java create mode 100644 src/ch2/doongu/ver1/Money.java create mode 100644 src/ch2/doongu/ver1/Movie.java create mode 100644 src/ch2/doongu/ver1/NoneDiscountPolicy.java create mode 100644 src/ch2/doongu/ver1/PercentDiscountPolicy.java create mode 100644 src/ch2/doongu/ver1/PeriodCondition.java create mode 100644 src/ch2/doongu/ver1/Reservation.java create mode 100644 src/ch2/doongu/ver1/Screening.java create mode 100644 src/ch2/doongu/ver1/SequenceCondition.java create mode 100644 src/ch2/doongu/ver2/AmountDiscountPolicy.java create mode 100644 src/ch2/doongu/ver2/CallExample.java create mode 100644 src/ch2/doongu/ver2/DefaultDiscountPolicy.java create mode 100644 src/ch2/doongu/ver2/DiscountCondition.java create mode 100644 src/ch2/doongu/ver2/DiscountPolicy.java create mode 100644 src/ch2/doongu/ver2/Money.java create mode 100644 src/ch2/doongu/ver2/Movie.java create mode 100644 src/ch2/doongu/ver2/NoneDiscountPolicy.java create mode 100644 src/ch2/doongu/ver2/PercentDiscountPolicy.java create mode 100644 src/ch2/doongu/ver2/PeriodCondition.java create mode 100644 src/ch2/doongu/ver2/Reservation.java create mode 100644 src/ch2/doongu/ver2/Screening.java create mode 100644 src/ch2/doongu/ver2/SequenceCondition.java diff --git a/src/ch2/doongu/ver1/AmountDiscountPolicy.java b/src/ch2/doongu/ver1/AmountDiscountPolicy.java new file mode 100644 index 0000000..dcc758e --- /dev/null +++ b/src/ch2/doongu/ver1/AmountDiscountPolicy.java @@ -0,0 +1,16 @@ +package ch2.doongu.ver1; + +// 금액 할인 정책 +public class AmountDiscountPolicy extends DiscountPolicy { + 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/ch2/doongu/ver1/CallExample.java b/src/ch2/doongu/ver1/CallExample.java new file mode 100644 index 0000000..600d0f7 --- /dev/null +++ b/src/ch2/doongu/ver1/CallExample.java @@ -0,0 +1,24 @@ +package ch2.doongu.ver1; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.LocalTime; + +public class CallExample { + Movie avater = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000), + new AmountDiscountPolicy(Money.wons(800), // AmountDiscountPolicy instance를 전달하기만 하면 금액 할인 정책이 적용된다. Movie 생성자에서 + // DiscountPolicy 타입의 객체를 인자로 받기 때문에 가능하다. + // '실행 시' Movie의 '인스턴스'는 AmountDiscountPolicy '인스턴스'에 의존하게 된다. + + // 할인 비율을 적용하고 싶다면 PercentDiscountPolicy를 전달하면 된다. + // 코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다. => 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있따. + // 확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다르다. + // 하지만 이는 양날의 검이다. 확장가능하면 의존성을 연결하는 부분을 찾아봐야한다. 설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 어려워진다. + // 반대로 코드를 이해하기 쉽고 디버깅하기 쉽다면 재사용성과 확장 가능성은 낮아진다. | 정답은 없다. 이것이 객체지향 설계가 어려우면서도 매력적인 이유다 ;;;; + 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)) + + )); +} diff --git a/src/ch2/doongu/ver1/DiscountCondition.java b/src/ch2/doongu/ver1/DiscountCondition.java new file mode 100644 index 0000000..bf5c7c6 --- /dev/null +++ b/src/ch2/doongu/ver1/DiscountCondition.java @@ -0,0 +1,10 @@ +package ch2.doongu.ver1; + +// 할인 조건. +public interface DiscountCondition { + //할인이 가능한 경우 True , 아니면 False + boolean isSatisfiedBy(Screening screening); +} + +// 할인 정책과 달리 할인 조건은 구현을 공유할 필요가 없기 때문에 (자식과) +// 자바의 인터페이스를 통해 구현해볼 수 있다. \ No newline at end of file diff --git a/src/ch2/doongu/ver1/DiscountPolicy.java b/src/ch2/doongu/ver1/DiscountPolicy.java new file mode 100644 index 0000000..68a8d55 --- /dev/null +++ b/src/ch2/doongu/ver1/DiscountPolicy.java @@ -0,0 +1,38 @@ +package ch2.doongu.ver1; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +//DiscountPolicy 안에 "중복 코드"를 두고 AmountDisCountPolicy와 PercentDiscountPolicy가 +// 이를 상속받게 함. 그래서 추상클래스로 만듬! + + +// 이 코드 처럼 부모 클래스에 기본적인 알고리즘 흐름을 구현하고 +// 중간에 필요한 처리를 자식 클래스에게 위임하는 디자인 패턴을 +// TEMPLATE METHOD 패턴이라고 부른다. + +// DiscountCondition은 인터페이스임. +public abstract class DiscountPolicy { + + //하나의 할인 정책은 여러개의 할인 조건을 가질 수 있따. + private List conditions = new ArrayList<>(); + + public DiscountPolicy(DiscountCondition... conditions) { + this.conditions = Arrays.asList(conditions); + } + + 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/ch2/doongu/ver1/Money.java b/src/ch2/doongu/ver1/Money.java new file mode 100644 index 0000000..ece806f --- /dev/null +++ b/src/ch2/doongu/ver1/Money.java @@ -0,0 +1,42 @@ +package ch2.doongu.ver1; + +import java.math.BigDecimal; + +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/ch2/doongu/ver1/Movie.java b/src/ch2/doongu/ver1/Movie.java new file mode 100644 index 0000000..4a24df1 --- /dev/null +++ b/src/ch2/doongu/ver1/Movie.java @@ -0,0 +1,58 @@ +package ch2.doongu.ver1; + +import java.time.Duration; + +public class Movie { + private String title; + private Duration runningTime; + 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; + } + + public Money getFee(){ + return fee; + } + + + // Movice 입장에서는 어떤 클래스의 인스턴스인지가 중요한 것이 아니라 + // calculateDiscountAmount 메세지를 수신할 수 있다는 사실이 중요하다. + // Movie는 동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 + // 무엇냐에 따라 달라진다. 이를 다형성이라고 부른다. + + // => 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미한다. + + // 프로그램 작성시에는 Movie 클래스는 추상 클래스에 의존하지만 + // 컴파일 시간 의존성은 Movie에서 DiscountPolicy로 향한다. + // 이처럼 다형성은 컴파일 시간 의존성과실행시간 의존성을 다르게 만들 수 있는 객체지향의 + // 특성을 이용해 서로 다른 메서드를 실행할 수 있게 한다. + + // 지연 바인딩(lazy binding) 또는 동적바인딩 이라는 의미는 메시지와 메서드를 실행 시점에 바인딩 하는 것이고 + // 이는 다형성을 구현 할 수 있다. + + // 전통적인 함수 호출 처럼 컴파일 시점에 실행될 함수나 프로시저를 결정하는 것을 + // 초기 바인딩(early binding) 또는 정적바인딩(static binding)이라고 부른다. + + // 동적바인딩 메커니즘 덕에 하나의 메시지를 선택적으로 서로 다른 메서드에 연결할 수 있다. + + // 상속은 "구현 상속"이 아니라 "인터페이스 상속"을 위해 사용해야한다. + // 사람들은 코드 재사용을 상속의 주된 목적이라고 생각하지만 이것은 오해다. + // 인터페이스 상속을 해야하는 이유는 진정한 다형성이 이루어지기 때문이 아닐까? + public Money calculationMovieFee(Screening screening){ + //스타워즈 허나 아래 코드처럼 작성하면 + // 할인 정책이 없는 경우를 예외 케이스로 취급하기 때문에 지금까지 일관성 있던 + // 협력 방식이 무너지게 된다. + // 기존 할인정책의 책임은 DiscountPollycy자식 클래스에만 있었는데 Movie에서 결정해버리기 때문 +// if (discountPolicy == null){ +// return fee; +// } + + return fee.minus(discountPolicy.calculateDiscountAmount(screening)); // + } + +} diff --git a/src/ch2/doongu/ver1/NoneDiscountPolicy.java b/src/ch2/doongu/ver1/NoneDiscountPolicy.java new file mode 100644 index 0000000..4c7bee4 --- /dev/null +++ b/src/ch2/doongu/ver1/NoneDiscountPolicy.java @@ -0,0 +1,21 @@ +package ch2.doongu.ver1; + +public class NoneDiscountPolicy extends DiscountPolicy { + public NoneDiscountPolicy(DiscountCondition... conditions) { + super(conditions); + } + + + //movie에 넣지않고 별도의 자식으로 관리한다. + // 추상화를 중심으로 유연하고 확장가능한 설계가 된다 + // 이렇게 하면 Movie는 특정한 할인 정책에 묶이지 않는다. + // 또한 DiscountPolicy는 어떤 클래스와도 협력이 가능하다. + + // 이 클래스 개발자는 DiscountPolicy의 내부 구현을 알고 있다. 그 가정하에 해당 메소드를 작성한 것 + // getDiscountAmount()가 호출되지 않을 경우 DiscountPolicy가 0원을 반환할 것이라는 사실을 가정하고 있다. + + @Override + protected Money getDiscountAmount(Screening Screening) { + return Money.ZERO; + } +} diff --git a/src/ch2/doongu/ver1/PercentDiscountPolicy.java b/src/ch2/doongu/ver1/PercentDiscountPolicy.java new file mode 100644 index 0000000..f9bfffb --- /dev/null +++ b/src/ch2/doongu/ver1/PercentDiscountPolicy.java @@ -0,0 +1,15 @@ +package ch2.doongu.ver1; + +public class PercentDiscountPolicy extends DiscountPolicy { + 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/ch2/doongu/ver1/PeriodCondition.java b/src/ch2/doongu/ver1/PeriodCondition.java new file mode 100644 index 0000000..efa16f4 --- /dev/null +++ b/src/ch2/doongu/ver1/PeriodCondition.java @@ -0,0 +1,25 @@ +package ch2.doongu.ver1; + +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; + } + + // 조건에 부합하는지 확인 + @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/ch2/doongu/ver1/Reservation.java b/src/ch2/doongu/ver1/Reservation.java new file mode 100644 index 0000000..f32bdaa --- /dev/null +++ b/src/ch2/doongu/ver1/Reservation.java @@ -0,0 +1,15 @@ +package ch2.doongu.ver1; + +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.audienceCount = audienceCount; + this.fee = fee; + this.screening = screening; + this.customer = customer; + } +} diff --git a/src/ch2/doongu/ver1/Screening.java b/src/ch2/doongu/ver1/Screening.java new file mode 100644 index 0000000..f5ffee7 --- /dev/null +++ b/src/ch2/doongu/ver1/Screening.java @@ -0,0 +1,36 @@ +package ch2.doongu.ver1; + +import java.time.LocalDateTime; + +public class Screening { + private final Movie movie; + private final int sequence; + private final LocalDateTime whenScreened; + + public Screening(Movie movie, int sequence, LocalDateTime whenScreened) { + this.movie = movie; + this.sequence = sequence; + this.whenScreened = whenScreened; + } + + // 상영 시작 시간을 반환 + public LocalDateTime getStartTime() { + return whenScreened; + } + + // 순번의 일치 여부를 검사 -> 할인 조건에 해당 + public boolean isSequence(int sequence) { + return this.sequence == sequence; + } + + // 기본 요금을 반환 + public Money getMovieFee() { + return movie.getFee(); + } + + //예매 정보 인스턴스 반환 + public Reservation reserve(Customer customer, int audienceCount){ + //요금을 계산해서 전달 + return new Reservation(customer, this, calculateFee(audienceCount), audienceCount); + } +} diff --git a/src/ch2/doongu/ver1/SequenceCondition.java b/src/ch2/doongu/ver1/SequenceCondition.java new file mode 100644 index 0000000..5bc71ef --- /dev/null +++ b/src/ch2/doongu/ver1/SequenceCondition.java @@ -0,0 +1,15 @@ +package ch2.doongu.ver1; + +public class SequenceCondition implements DiscountCondition { + private int squence; + + public SequenceCondition(int sequence){ + this.squence = sequence; + } + + + @Override + public boolean isSatisfiedBy(Screening screening) { + return screening.isSequence(squence); + } +} diff --git a/src/ch2/doongu/ver2/AmountDiscountPolicy.java b/src/ch2/doongu/ver2/AmountDiscountPolicy.java new file mode 100644 index 0000000..a1705b5 --- /dev/null +++ b/src/ch2/doongu/ver2/AmountDiscountPolicy.java @@ -0,0 +1,16 @@ +package ch2.doongu.ver2; + +// 금액 할인 정책 +public class AmountDiscountPolicy extends DiscountPolicy { + 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/ch2/doongu/ver2/CallExample.java b/src/ch2/doongu/ver2/CallExample.java new file mode 100644 index 0000000..b237cb8 --- /dev/null +++ b/src/ch2/doongu/ver2/CallExample.java @@ -0,0 +1,24 @@ +package ch2.doongu.ver2; + +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.LocalTime; + +public class CallExample { + Movie avater = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000), + new AmountDiscountPolicy(Money.wons(800), // AmountDiscountPolicy instance를 전달하기만 하면 금액 할인 정책이 적용된다. Movie 생성자에서 + // DiscountPolicy 타입의 객체를 인자로 받기 때문에 가능하다. + // '실행 시' Movie의 '인스턴스'는 AmountDiscountPolicy '인스턴스'에 의존하게 된다. + + // 할인 비율을 적용하고 싶다면 PercentDiscountPolicy를 전달하면 된다. + // 코드의 의존성과 실행 시점의 의존성이 서로 다를 수 있다. => 클래스 사이의 의존성과 객체 사이의 의존성은 동일하지 않을 수 있따. + // 확장 가능한 객체지향 설계가 가지는 특징은 코드의 의존성과 실행 시점의 의존성이 다르다. + // 하지만 이는 양날의 검이다. 확장가능하면 의존성을 연결하는 부분을 찾아봐야한다. 설계가 유연해질수록 코드를 이해하고 디버깅하기는 점점 더 어려워진다. + // 반대로 코드를 이해하기 쉽고 디버깅하기 쉽다면 재사용성과 확장 가능성은 낮아진다. | 정답은 없다. 이것이 객체지향 설계가 어려우면서도 매력적인 이유다 ;;;; + 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)) + + )); +} diff --git a/src/ch2/doongu/ver2/DefaultDiscountPolicy.java b/src/ch2/doongu/ver2/DefaultDiscountPolicy.java new file mode 100644 index 0000000..c950d79 --- /dev/null +++ b/src/ch2/doongu/ver2/DefaultDiscountPolicy.java @@ -0,0 +1,34 @@ +package ch2.doongu.ver2; + +import ch2.doongu.ver1.DiscountCondition; +import ch2.doongu.ver1.Money; +import ch2.doongu.ver1.Screening; + +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); + } + + 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/ch2/doongu/ver2/DiscountCondition.java b/src/ch2/doongu/ver2/DiscountCondition.java new file mode 100644 index 0000000..f81ce7a --- /dev/null +++ b/src/ch2/doongu/ver2/DiscountCondition.java @@ -0,0 +1,10 @@ +package ch2.doongu.ver2; + +// 할인 조건. +public interface DiscountCondition { + //할인이 가능한 경우 True , 아니면 False + boolean isSatisfiedBy(Screening screening); +} + +// 할인 정책과 달리 할인 조건은 구현을 공유할 필요가 없기 때문에 (자식과) +// 자바의 인터페이스를 통해 구현해볼 수 있다. \ No newline at end of file diff --git a/src/ch2/doongu/ver2/DiscountPolicy.java b/src/ch2/doongu/ver2/DiscountPolicy.java new file mode 100644 index 0000000..a6727c4 --- /dev/null +++ b/src/ch2/doongu/ver2/DiscountPolicy.java @@ -0,0 +1,16 @@ +package ch2.doongu.ver2; + +//DiscountPolicy 안에 "중복 코드"를 두고 AmountDisCountPolicy와 PercentDiscountPolicy가 +// 이를 상속받게 함. 그래서 추상클래스로 만듬! + + +// 이 코드 처럼 부모 클래스에 기본적인 알고리즘 흐름을 구현하고 +// 중간에 필요한 처리를 자식 클래스에게 위임하는 디자인 패턴을 +// TEMPLATE METHOD 패턴이라고 부른다. + +// DiscountCondition은 인터페이스임. + +//DiscountPolicy를 dlsxjvpdltmfh qusrudgkwk. +public interface DiscountPolicy { + Money calculateDiscountAmount(Screening screening); +} diff --git a/src/ch2/doongu/ver2/Money.java b/src/ch2/doongu/ver2/Money.java new file mode 100644 index 0000000..bfbd8f8 --- /dev/null +++ b/src/ch2/doongu/ver2/Money.java @@ -0,0 +1,42 @@ +package ch2.doongu.ver2; + +import java.math.BigDecimal; + +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/ch2/doongu/ver2/Movie.java b/src/ch2/doongu/ver2/Movie.java new file mode 100644 index 0000000..ccb9eda --- /dev/null +++ b/src/ch2/doongu/ver2/Movie.java @@ -0,0 +1,58 @@ +package ch2.doongu.ver2; + +import java.time.Duration; + +public class Movie { + private String title; + private Duration runningTime; + 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; + } + + public Money getFee(){ + return fee; + } + + + // Movice 입장에서는 어떤 클래스의 인스턴스인지가 중요한 것이 아니라 + // calculateDiscountAmount 메세지를 수신할 수 있다는 사실이 중요하다. + // Movie는 동일한 메시지를 전송하지만 실제로 어떤 메서드가 실행될 것인지는 메시지를 수신하는 객체의 클래스가 + // 무엇냐에 따라 달라진다. 이를 다형성이라고 부른다. + + // => 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력을 의미한다. + + // 프로그램 작성시에는 Movie 클래스는 추상 클래스에 의존하지만 + // 컴파일 시간 의존성은 Movie에서 DiscountPolicy로 향한다. + // 이처럼 다형성은 컴파일 시간 의존성과실행시간 의존성을 다르게 만들 수 있는 객체지향의 + // 특성을 이용해 서로 다른 메서드를 실행할 수 있게 한다. + + // 지연 바인딩(lazy binding) 또는 동적바인딩 이라는 의미는 메시지와 메서드를 실행 시점에 바인딩 하는 것이고 + // 이는 다형성을 구현 할 수 있다. + + // 전통적인 함수 호출 처럼 컴파일 시점에 실행될 함수나 프로시저를 결정하는 것을 + // 초기 바인딩(early binding) 또는 정적바인딩(static binding)이라고 부른다. + + // 동적바인딩 메커니즘 덕에 하나의 메시지를 선택적으로 서로 다른 메서드에 연결할 수 있다. + + // 상속은 "구현 상속"이 아니라 "인터페이스 상속"을 위해 사용해야한다. + // 사람들은 코드 재사용을 상속의 주된 목적이라고 생각하지만 이것은 오해다. + // 인터페이스 상속을 해야하는 이유는 진정한 다형성이 이루어지기 때문이 아닐까? + public Money calculationMovieFee(Screening screening){ + //스타워즈 허나 아래 코드처럼 작성하면 + // 할인 정책이 없는 경우를 예외 케이스로 취급하기 때문에 지금까지 일관성 있던 + // 협력 방식이 무너지게 된다. + // 기존 할인정책의 책임은 DiscountPollycy자식 클래스에만 있었는데 Movie에서 결정해버리기 때문 +// if (discountPolicy == null){ +// return fee; +// } + + return fee.minus(discountPolicy.calculateDiscountAmount(screening)); // + } + +} diff --git a/src/ch2/doongu/ver2/NoneDiscountPolicy.java b/src/ch2/doongu/ver2/NoneDiscountPolicy.java new file mode 100644 index 0000000..b57f654 --- /dev/null +++ b/src/ch2/doongu/ver2/NoneDiscountPolicy.java @@ -0,0 +1,8 @@ +package ch2.doongu.ver2; + +public class NoneDiscountPolicy implements DiscountPolicy { + @Override + public Money calculateDiscountAmount(Screening screening){ + return Money.ZERO; + } +} diff --git a/src/ch2/doongu/ver2/PercentDiscountPolicy.java b/src/ch2/doongu/ver2/PercentDiscountPolicy.java new file mode 100644 index 0000000..e3e9d24 --- /dev/null +++ b/src/ch2/doongu/ver2/PercentDiscountPolicy.java @@ -0,0 +1,15 @@ +package ch2.doongu.ver2; + +public class PercentDiscountPolicy extends DiscountPolicy { + 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/ch2/doongu/ver2/PeriodCondition.java b/src/ch2/doongu/ver2/PeriodCondition.java new file mode 100644 index 0000000..1396924 --- /dev/null +++ b/src/ch2/doongu/ver2/PeriodCondition.java @@ -0,0 +1,25 @@ +package ch2.doongu.ver2; + +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; + } + + // 조건에 부합하는지 확인 + @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/ch2/doongu/ver2/Reservation.java b/src/ch2/doongu/ver2/Reservation.java new file mode 100644 index 0000000..3bfaf4f --- /dev/null +++ b/src/ch2/doongu/ver2/Reservation.java @@ -0,0 +1,15 @@ +package ch2.doongu.ver2; + +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.audienceCount = audienceCount; + this.fee = fee; + this.screening = screening; + this.customer = customer; + } +} diff --git a/src/ch2/doongu/ver2/Screening.java b/src/ch2/doongu/ver2/Screening.java new file mode 100644 index 0000000..c169cca --- /dev/null +++ b/src/ch2/doongu/ver2/Screening.java @@ -0,0 +1,36 @@ +package ch2.doongu.ver2; + +import java.time.LocalDateTime; + +public class Screening { + private final Movie movie; + private final int sequence; + private final LocalDateTime whenScreened; + + public Screening(Movie movie, int sequence, LocalDateTime whenScreened) { + this.movie = movie; + this.sequence = sequence; + this.whenScreened = whenScreened; + } + + // 상영 시작 시간을 반환 + public LocalDateTime getStartTime() { + return whenScreened; + } + + // 순번의 일치 여부를 검사 -> 할인 조건에 해당 + public boolean isSequence(int sequence) { + return this.sequence == sequence; + } + + // 기본 요금을 반환 + public Money getMovieFee() { + return movie.getFee(); + } + + //예매 정보 인스턴스 반환 + public Reservation reserve(Customer customer, int audienceCount){ + //요금을 계산해서 전달 + return new Reservation(customer, this, calculateFee(audienceCount), audienceCount); + } +} diff --git a/src/ch2/doongu/ver2/SequenceCondition.java b/src/ch2/doongu/ver2/SequenceCondition.java new file mode 100644 index 0000000..3aee484 --- /dev/null +++ b/src/ch2/doongu/ver2/SequenceCondition.java @@ -0,0 +1,15 @@ +package ch2.doongu.ver2; + +public class SequenceCondition implements DiscountCondition { + private int squence; + + public SequenceCondition(int sequence){ + this.squence = sequence; + } + + + @Override + public boolean isSatisfiedBy(Screening screening) { + return screening.isSequence(squence); + } +}