49. –할인 적용
상영정보
2010년 12월 23일
목요일
18:00 ~ 20:00(7회차)
10회 상영인 경우
이끼
8000원
Amount DC
800원
조조 상영인 경우
월요일 10:00 ~ 12:00 상영인 경우
목요일 18:00 ~ 21:00 상영인 경우
7,200원
70. 31 / 문서의 제목
절차적인 예매 로직
@Transactional
public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {
① 데이터베이스로부터 Movie, Showing, Rule 조회
② Showing에 적용할 수 있는 Rule 이 존재하는지 판단
③ if (Rule 이 존재하면) {
Discount를 읽어 요금 할인된 요금 계산
} else {
Movie의 정가를 이용해 요금 계산
}
④ Reservation 생성 후 데이터베이스 저장
}
71. 32 / 문서의 제목
@Override
public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {
Showing showing = showingDAO.selectShowing(showingId);
Movie movie = movieDAO.selectMovie(showing.getMovieId());
ListRule rules = ruleDAO.selectRules(movie.getId());
② Showing에 적용할 수 있는 Rule 이 존재하는지 판단
③ if (Rule 이 존재하면) {
Discount를 읽어 요금 할인된 요금 계산
} else {
Movie의 정가를 이용해 요금 계산
}
④ Reservation 생성 후 데이터베이스 저장
}
① 데이터베이스로부터 Movie, Showing, Rule 조회
절차적인 예매 로직
72. 33 / 문서의 제목
@Transactional
public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {
Showing showing = showingDAO.selectShowing(showingId);
Movie movie = movieDAO.selectMovie(showing.getMovieId());
ListRule rules = ruleDAO.selectRules(movie.getId());
Rule rule = findRule(showing, rules);
③ if (Rule 이 존재하면) {
Discount를 읽어 요금 할인된 요금 계산
} else {
Movie의 정가를 이용해 요금 계산
}
④ Reservation 생성 후 데이터베이스 저장
}
절차적인 예매 로직
② Showing에 적용할 수 있는 Rule 이 존재하는지 판단
private Rule findRule(Showing showing, ListRule rules) {
for(Rule rule : rules) {
if (rule.isTimeOfDayRule()) {
if (showing.isDayOfWeek(rule.getDayOfWeek())
showing.isDurationBetween(rule.getStartTime(), rule.getEndTime())) {
return rule;
}
} else {
if (rule.getSequence() == showing.getSequence()) {
return rule;
}
}
}
return null;
}
73. 34 / 문서의 제목
@Transactional
public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {
Showing showing = showingDAO.selectShowing(showingId);
Movie movie = movieDAO.selectMovie(showing.getMovieId());
ListRule rules = ruleDAO.selectRules(movie.getId());
Rule rule = findRule(showing, rules);
Money fee = movie.getFee();
if (rule != null) {
fee = calculateFee(movie);
}
④ Reservation 생성 후 데이터베이스 저장
}
절차적인 예매 로직
③ if (Rule 이 존재하면) {
Discount를 읽어 요금 할인된 요금 계산
} else {
Movie의 정가를 이용해 요금 계산
}
private Money calculateFee(Movie movie) {
Discount discount = discountDAO.selectDiscount(movie.getId());
Money discountFee = Money.ZERO;
if (discount != null) {
if (discount.isAmountType()) {
discountFee = Money.wons(discount.getFee());
} else if (discount.isPercentType()) {
discountFee = movie.getFee().times(discount.getPercent());
}
}
return movie.getFee().minus(discountFee);
}
74. 35 / 문서의 제목
@Transactional
public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {
Showing showing = showingDAO.selectShowing(showingId);
Movie movie = movieDAO.selectMovie(showing.getMovieId());
ListRule rules = ruleDAO.selectRules(movie.getId());
Rule rule = findRule(showing, rules);
Money fee = movie.getFee();
if (rule != null) {
fee = calculateFee(movie);
}
Reservation result = makeReservation(customerId, showingId, audienceCount, fee);
reservationDAO.insert(result);
return result;
}
절차적인 예매 로직
private Reservation makeReservation(int customerId, int showingId,
int audienceCount, Money payment) {
Reservation result = new Reservation();
result.setCustomerId(customerId);
result.setShowingId(showingId);
result.setAudienceCount(audienceCount);
result.setFee(payment);
return result;
}
④ Reservation 생성 후 데이터베이스 저장
75. 36 / 문서의 제목
중앙 집중식Centralized 제어 스타일
reserveShowing()
showing = selectShowing()
rules = selectRules()
isTimeOfDayRule()*
discount = selectDiscount()
isAmountType()
getFee()
new
rules
:Rule
:Showing
DAO
showing
:Showing
:Reservation
Script
:Rule
DAO
:Discount
DAO
discount
:Discount
:Reservation
91. 할인율 계산 책임 할당
할인율을 적용할 책임을 가진 객체 추가
Movie
영화 정보를 알고 있다
가격을 계산한다
Showing
상영 정보를 알고 있다
예매 정보를 생성한다
Movie
Customer
Discount Strategy
Discount Strategy
할인 정책을 알고 있다
할인된 가격을 계산한다
92. 할인 여부를 판단할 책임 할당
할인 정책을 판단하는 책임을 가진 Specification
93. 객체 추가
Movie
영화 정보를 알고 있다
가격을 계산한다
Showing
상영 정보를 알고 있다
예매 정보를 생성한다
Movie
Customer
Discount Strategy
할인 정책을 알고 있다
할인된 가격을 계산한다
Discount Strategy
Rule
할인 규칙을 알고 있다
할인 여부를 판단한다
Rule
94. public class Showing {
public Reservation reserve(Customer customer, int audienceCount) {
return new Reservation(customer, this, audienceCount);
}
}
public class Reservation {
public Reservation(Customer customer, Showing showing, int audienceCount) {
this.customer = customer;
this.showing = showing;
this.fee = showing.calculateFee().times(audienceCount);
this.audienceCount = audienceCount;
}
}
public class Showing {
public Money calculateFee() {
return movie.calculateFee(this);
}
}
public class Movie {
public Money calculateFee(Showing showing) {
return fee.minus(discountStrategy.calculateDiscountFee(showing));
}
}
객체지향 구현
95. public abstract class DiscountStrategy {
public Money calculateDiscountFee(Showing showing) {
for(Rule each : rules) {
if (each.isStatisfiedBy(showing)) {
return getDiscountFee(showing);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountFee(Showing showing);
public interface Rule {
boolean isStatisfiedBy(Showing showing);
}
public class SequenceRule implements Rule {
public boolean isStatisfiedBy(Showing showing) {
return showing.isSequence(sequence);
}
}
public class TimeOfDayRule implements Rule {
public boolean isStatisfiedBy(Showing showing) {
return showing.isPlayingOn(dayOfWeek)
Interval.closed(startTime, endTime)
.includes(showing.getPlayngInterval());
}
}
객체지향 구현
96. 객체지향 구현
public abstract class DiscountStrategy {
public Money calculateDiscountFee(Showing showing) {
for(Rule each : rules) {
if (each.isStatisfiedBy(showing)) {
return getDiscountFee(showing);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountFee(Showing showing);
public class AmountDiscountStrategy extends DiscountStrategy {
protected Money getDiscountFee(Showing showing) {
return discountAmount;
}
}
public class NonDiscountStrategy extends DiscountStrategy {
protected Money getDiscountFee(Showing showing) {
return Money.ZERO;
}
}
public class PercentDiscountStrategy extends DiscountStrategy {
protected Money getDiscountFee(Showing showing) {
return showing.getFixedFee().times(percent);
}
}
108. 58 / 문서의 제목
도메인 모델을 사용할 때
Showing
reserve(customer, count):Reservation
Rule
isStatisfiedBy(showing):boolean
DiscountStrategy
calculateFee(showing):Money
Movie
calculateFee(showing):Money
create
Customer
Reservation
AmountDiscount
Strategy
PercentDiscount
Strategy
NonDiscountStrategy SequenceRule TimeOfDayRule
125. 67 / 문서의 제목
도메인 모델을 사용할 때
Showing
reserve(customer, count):Reservation
Rule
isStatisfiedBy(showing):boolean
DiscountStrategy
calculateFee(showing):Money
Movie
calculateFee(showing):Money
create
Customer
Reservation
AmountDiscount
Strategy
PercentDiscount
Strategy
NonDiscountStrategy SequenceRule TimeOfDayRule
126. 68 / 문서의 제목
객체-관계 임피던스 불일치
RULE
ID
DISCOUNT_ID(FK)
POSITION
RULE_TYPE
DAY_OF_WEEK
START_TIME
END_TUME
SEQUENCE
DISCOUNT
MOVIE_ID(FK)
DISCOUNT_TYPE
FEE_AMOUNT
FEE_CURRENCY
PERCENT
Rule
Amount
Strategy
Percent
Strategy
NonDiscount
Strategy
Sequence
Rule
TimeOfDay
Rule
DiscountStrategy
객체 모델과 DB
140. 73 / 문서의 제목
데이터 소스 레이어
MOVIE
ID
TITLE
RUNNING_TIME
FEE_AMOUNT
FEE_CURRENCY
RESERVATION
ID
CUSTOMER_ID(FK)
SHOWING_ID(FK)
FEE_AMOUNT
FEE_CURRENCY
AUDIENCE_COUNT
RULE
ID
DISCOUNT_ID(FK)
POSITION
RULE_TYPE
DAY_OF_WEEK
START_TIME
END_TUME
SEQUENCE
DISCOUNT
MOVIE_ID(FK)
DISCOUNT_TYPE
FEE_AMOUNT
FEE_CURRENCY
PERCENT
SHOWING
ID
MOVIE_ID(FK)
SEQUENCE
SHOWING_TIME
CUSTOMER
ID
CUSTOMER_ID
NAME
176. –할인 적용
상영정보
2010년 12월 23일
목요일
18:00 ~ 20:00(7회차)
10회 상영인 경우
이끼
8000원
조조 상영인 경우
월요일 10:00 ~ 12:00 상영인 경우
목요일 18:00 ~ 21:00 상영인 경우
Amount DC
800원
Percent DC
5%
(400원)
179. –할인 적용
상영정보
2010년 12월 23일
목요일
18:00 ~ 20:00(7회차)
10회 상영인 경우
이끼
8000원
조조 상영인 경우
월요일 10:00 ~ 12:00 상영인 경우
목요일 18:00 ~ 21:00 상영인 경우
Amount DC
800원
Percent DC
5%
(400원)
6,800원
180. 84 / 문서의 제목
@Transactional
public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {
Showing showing = showingDAO.selectShowing(showingId);
Movie movie = movieDAO.selectMovie(showing.getMovieId());
ListRule rules = ruleDAO.selectRules(movie.getId());
Rule rule = findRule(showing, rules);
Money fee = movie.getFee();
if (rule != null) {
fee = calculateFee(movie);
}
Reservation result = makeReservation(customerId, showingId, audienceCount, fee);
reservationDAO.insert(result);
return result;
}
트랜잭션 스크립트를 사용할 경우
private Money calculateFee(Movie movie) {
Discount discount = discountDAO.selectDiscount(movie.getId());
Money discountFee = Money.ZERO;
if (discount != null) {
if (discount.isAmountType()) {
discountFee = Money.wons(discount.getFee());
} else if (discount.isPercentType()) {
discountFee = movie.getFee().times(discount.getPercent());
}
}
return movie.getFee().minus(discountFee);
}
181. 85 / 문서의 제목
@Transactional
public Reservation reserveShowing(int customerId, int showingId, int audienceCount) {
Showing showing = showingDAO.selectShowing(showingId);
Movie movie = movieDAO.selectMovie(showing.getMovieId());
ListRule rules = ruleDAO.selectRules(movie.getId());
Rule rule = findRule(showing, rules);
Money fee = movie.getFee();
if (rule != null) {
fee = calculateFee(movie);
}
Reservation result = makeReservation(customerId, showingId, audienceCount, fee);
reservationDAO.insert(result);
return result;
}
트랜잭션 스크립트를 사용할 경우
private Money calculateFee(Movie movie) {
ListDiscount discounts = discountDAO.selectDiscounts(movie.getId());
Money discountFee = Money.ZERO;
for(Discount discount : discounts) {
if (discount.isAmountType()) {
discountFee = discountFee.plus(Money.wons(discount.getFee()));
} else if (discount.isPercentType()) {
discountFee = discountFee.plus(movie.getFee().times(discount.getPercent());
}
}
return movie.getFee().minus(discountFee);
}
185. 89 / 문서의 제목
도메인 모델을 사용할 때
Showing
reserve(customer, count):Reservation
Rule
isStatisfiedBy(showing):boolean
DiscountStrategy
calculateFee(showing):Money
Movie
calculateFee(showing):Money
create
Customer
Reservation
AmountDiscount
Strategy
PercentDiscount
Strategy
NonDiscountStrategy SequenceRule TimeOfDayRule
186. 90 / 문서의 제목
:Overlapped
DiscountStrategy
컴포지트COMPOSITE 디자인 패턴
:Movie
:Amount
Strategy
calculateFee()calculateFee()
:Movie
:Amount
Strategy
calculateFee()calculateFee()
:Percent
Strategy
Movie의 입장에서는
하나의 DiscountStrategy나 여러개의 DiscountStrategy나 동일
187. 91 / 문서의 제목
public abstract class DiscountStrategy {
public Money calculateDiscountFee(Showing showing) {
for(Rule each : rules) {
if (each.isStatisfiedBy(showing)) {
return getDiscountFee(showing);
}
}
return Money.ZERO;
}
abstract protected Money getDiscountFee(Showing showing);
public class AmountDiscountStrategy extends DiscountStrategy {
protected Money getDiscountFee(Showing showing) {
return discountAmount;
}
}
public class NonDiscountStrategy extends DiscountStrategy {
protected Money getDiscountFee(Showing showing) {
return Money.ZERO;
}
}
public class PercentDiscountStrategy extends DiscountStrategy {
protected Money getDiscountFee(Showing showing) {
return showing.getFixedFee().times(percent);
}
}
public class OverlappedDiscountStrategy extends DiscountStrategy {
private ListDiscountStrategy discountStrategies = new ArrayList();
public OverlappedDiscountStrategy(DiscountStrategy... discountStrategies) {
this.discountStrategies = Arrays.asList(discountStrategies);
}
@Override
protected Money getDiscountFee(Showing showing) {
Money result = Money.ZERO;
for(DiscountStrategy each : discountStrategies) {
result = result.plus(each.calculateDiscountFee(showing));
}
return result;
}
}
새로운 DiscountStrategy 클래스 추가
개념을 명시적으로 표현
188. 92 / 문서의 제목
도메인 모델을 사용할 때
Showing
reserve(customer, count):Reservation
Rule
isStatisfiedBy(showing):boolean
DiscountStrategy
calculateFee(showing):Money
Movie
calculateFee(showing):Money
create
Customer
Reservation
AmountDiscount
Strategy
PercentDiscount
Strategy
NonDiscountStrategy SequenceRule TimeOfDayRule
189. 93 / 문서의 제목
Showing
reserve(customer, count):Reservation
Rule
isStatisfiedBy(showing):boolean
DiscountStrategy
calculateFee(showing):Money
Movie
calculateFee(showing):Money
create
Customer
Reservation
AmountDiscount
Strategy
PercentDiscount
Strategy
NonDiscount
Strategy
SequenceRule TimeOfDayRule
Overlapped
DiscountStrategy
*
객체지향의 장점
기존 코드를 수정하지 않고 새로운 클래스를 추가
206. 101 / 문서의 제목
[도메인 모델은] 복잡성을 알고리즘에서 분리하고 객체 간의 관계로
만들 수 있다.
유효성 검사, 계산, 파생 등이 포함된 복잡하고 끊임없이 변하는
비즈니스 규칙을 구현해야 한다면 객체 모델을 사용해 비즈니스 규칙을
처리하는 것이 현명하다.
- Martin
210. 104 / 문서의 제목
대부분 현재 코드는 요구 사항 변경에 부적합
Rule
isStatisfiedBy(showing):boolean
AmountStrategy PercentStrategy NonDiscountStrategy SequenceRule TimeOfDayRule
DiscountStrategy
calculateFee(showing):Money
Movie
calculateFee(showing):Money
Movie
calculateFee(showing):Money
우리의 상태
우리의 목표
213. 107 / 문서의 제목
OO 패러다임이 변화를 수용할 수 있는 설계를 지원
Showing
reserve(customer, count):Reservation
Rule
isStatisfiedBy(showing):boolean
DiscountStrategy
calculateFee(showing):Money
Movie
calculateFee(showing):Money
create
Customer
Reservation
AmountDiscount
Strategy
PercentDiscount
Strategy
NonDiscountStrategy SequenceRule TimeOfDayRule