예제링크
이렇게 책임이 정해졌으니 책임에 필요한 인스턴스 변수를 결정.
상영시간 및 상영 순번을 인스턴스 변수로 포함
Moive에 가격을 계산하라는 메시지를 전송해야 하기 때문에 Moive에 대한 참조도 포함
public class Screening {
private Movie movie;
private int sequence;
private LocalDateTime whenScreened;
public Reservation reserve(Customer customer, int audienceCount) {
return new Reservation(customer, this, calculateFee(audienceCount), audienceCount);
}
private Money calculateFee(int audienceCount) {
return movie.calculateMovieFee(this).times(audienceCount);
}
public LocalDateTime getWhenScreened() {
return whenScreened;
}
public int getSequence() {
return sequence;
}
}
calculateFee는 수신자인 moive가 아니라 송신자인 Screening의 의도를 표현한것
moive에 내부구현에 대한 어떤 지식도 없이 전송할 메시지를 결정했다는 것이다.
이렇게 movie의 구현을 고려하지 않고 필요한 메시지를 결정하면 Moive의 내부 구현을 깔끔하게 캡슐화 할수 있다.
Moive와 Screening의 연결관계는 메시지 뿐이다. 따라서 moive의 내부구현에 어떠한 수정을 가하더라도 Screening에는 영향을 미치지 않는다.
메시지를 기반으로 협력을 구성하면 Screening와 Moive 사이의 결합도를 느슨하게 유지할수있다.
Movie를 구현
Screening에서 보낸 메시지를 응답하는 메서드를 구현해야 한다.
public class Movie {
private String title;
private Duration runningTime;
private Money fee;
private MovieType movieType;
private Money discountAmount;
private double discountPercent;
private List<PeriodCondition> periodConditions;
private List<SequenceCondition> sequenceConditions;
public Movie(String title, Duration runningTime, Money fee,
List<PeriodCondition> periodConditions, List<SequenceCondition> sequenceConditions) {
this.title = title;
this.runningTime = runningTime;
this.fee = fee;
this.periodConditions = periodConditions;
this.sequenceConditions = sequenceConditions;
}
public Money calculateMovieFee(Screening screening) {
if (isDiscountable(screening)) {
return fee.minus(calculateDiscountAmount());
}
return fee;
}
private boolean isDiscountable(Screening screening) {
return checkPeriodConditions(screening) ||
checkSequenceConditions(screening);
}
private boolean checkPeriodConditions(Screening screening) {
return periodConditions.stream()
.anyMatch(condition -> condition.isSatisfiedBy(screening));
}
private boolean checkSequenceConditions(Screening screening) {
return sequenceConditions.stream()
.anyMatch(condition -> condition.isSatisfiedBy(screening));
}
private Money calculateDiscountAmount() {
switch(movieType) {
case AMOUNT_DISCOUNT:
return calculateAmountDiscountAmount();
case PERCENT_DISCOUNT:
return calculatePercentDiscountAmount();
case NONE_DISCOUNT:
return calculateNoneDiscountAmount();
}
throw new IllegalStateException();
}
private Money calculateAmountDiscountAmount() {
return discountAmount;
}
private Money calculatePercentDiscountAmount() {
return fee.times(discountPercent);
}
private Money calculateNoneDiscountAmount() {
return Money.ZERO;
}
}
public enum MovieType {
AMOUNT_DISCOUNT, // 금액 할인 정책
PERCENT_DISCOUNT, // 비율 할인 정책
NONE_DISCOUNT // 미적용
}
public class DiscountCondition {
private DiscountConditionType type;
private int sequence;
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
public boolean isSatisfiedBy(Screening screening) {
if (type == DiscountConditionType.PERIOD) {
return isSatisfiedByPeriod(screening);
}
return isSatisfiedBySequence(screening);
}
private boolean isSatisfiedByPeriod(Screening screening) {
return dayOfWeek.equals(screening.getWhenScreened().getDayOfWeek()) &&
startTime.compareTo(screening.getWhenScreened().toLocalTime()) <= 0 &&
endTime.compareTo(screening.getWhenScreened().toLocalTime()) <= 0;
}
private boolean isSatisfiedBySequence(Screening screening) {
return sequence == screening.getSequence();
}
}
변경에 취약한 클래스를 포함하고 있다.
그것은 바로 DiscountCondition이다. 메시지를 보내는것이아닌 값을 받아와서 처리하고 있기 때문이다.
3가지 이유로 변경될수 있다.
DiscountCondition은 하나 이상의 변경 이유를 가지기 때문에 응집도가 낮다.
응집도가 낮다는것은 서로 연관성이 없는 기능이나 데이터가 하나의 클래스안에 뭉쳐져있다는것을 의미한다.
낮은 응집도가 유발하는 문제를 해결하기 위해서는 변경의 이유에 따라 클래스를 분리해야 한다
코드를 통해 변경의 이유를 파악할 수 있는 첫번째 방법은 인스턴스 변수가 초기화 되는 시점 을 살펴보는 것이다
응집도가 높은 클래스는 인스턴스를 생성할때 모든 속성이 함께 초기화 된다.
반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화 되지 않은 상태로 남겨진다.
DiscountCondition을 살펴보면 조건에 따라 특정 인스턴스변수만 초기화된다.
클래스의 속성이 서로 다른시점에 초기화되거나 일부만 초기화된다는 것은 응집도가 낮다는 증거다.
따라서 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다
코드를 통해 변경의 이유를 파악할수 있는 두번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식 을 살펴보는 것이다.
모든 메서드가 객체의 모든 속성을 사용한다면 클래스의 응집도는 높다고 볼수 있다.
반면 메소드를 사용하는 속성에 따라 그룹이 나뉜다면 클래스의 응집도가 낮다고 볼수 있다.
특정메서드는 특정 필드만 사용한다
이럴경우 응집도를 높이기 위해서 속성 그룹과 해당 그룹에 접근하는 메소드 그룹을 기준으로 코드를 분리해야 한다