Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 16 additions & 0 deletions src/ch2/doongu/ver1/AmountDiscountPolicy.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
24 changes: 24 additions & 0 deletions src/ch2/doongu/ver1/CallExample.java
Original file line number Diff line number Diff line change
@@ -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))

));
}
10 changes: 10 additions & 0 deletions src/ch2/doongu/ver1/DiscountCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ch2.doongu.ver1;

// 할인 조건.
public interface DiscountCondition {
//할인이 가능한 경우 True , 아니면 False
boolean isSatisfiedBy(Screening screening);
}

// 할인 정책과 달리 할인 조건은 구현을 공유할 필요가 없기 때문에 (자식과)
// 자바의 인터페이스를 통해 구현해볼 수 있다.
38 changes: 38 additions & 0 deletions src/ch2/doongu/ver1/DiscountPolicy.java
Original file line number Diff line number Diff line change
@@ -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<DiscountCondition> 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);
}
42 changes: 42 additions & 0 deletions src/ch2/doongu/ver1/Money.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
58 changes: 58 additions & 0 deletions src/ch2/doongu/ver1/Movie.java
Original file line number Diff line number Diff line change
@@ -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)); //
}

}
21 changes: 21 additions & 0 deletions src/ch2/doongu/ver1/NoneDiscountPolicy.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
15 changes: 15 additions & 0 deletions src/ch2/doongu/ver1/PercentDiscountPolicy.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
25 changes: 25 additions & 0 deletions src/ch2/doongu/ver1/PeriodCondition.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
15 changes: 15 additions & 0 deletions src/ch2/doongu/ver1/Reservation.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
36 changes: 36 additions & 0 deletions src/ch2/doongu/ver1/Screening.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
15 changes: 15 additions & 0 deletions src/ch2/doongu/ver1/SequenceCondition.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
16 changes: 16 additions & 0 deletions src/ch2/doongu/ver2/AmountDiscountPolicy.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
24 changes: 24 additions & 0 deletions src/ch2/doongu/ver2/CallExample.java
Original file line number Diff line number Diff line change
@@ -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))

));
}
Loading