PeriodCondition이 Screening을 의존하는 예제
public class PeriodCondition implements DiscountCondition {
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
public boolean isSatisfiedBy(Screening screening) {
return screening.getStartTime().getDayOfWeek().equals(dayofWeek) &&
...
}
}
PeriodCondition은 DayOfWeek, LocalTime, Screening에 을 의존한다.DiscountCondition에도 의존하는데 이유는 인터페이스에 정의된 오퍼레이션들을 퍼블릭 인터페이스 일부로 포함시키기 위해서다.PeriodCondition 인스턴스가 동작하려면 Screening 인스턴스가 있어야 한다.Screening이 변경될 때 PeriodCondition은 영향을 받지만 역은 성립하지 않는다.PeriodCondition이 Screening에 의존할 경우 PeriodCondition은 Screening이 의존하는 대상에 대해서도 자동으로 의존하게 된다.PeriodCondition이 Screening이 의존하는 Movie, LocalDateTime, Customer를 간접적으로 의존하게 되는 것setter 메서드를 통해 의존성 해결setter 방식을 혼합하는 것
setter 메서드로 의존 대상 변경의존성 관점에서는 의존성이 존재한다 또는 존재하지 않는다라고 표현하지만 결합도 관점에선 의존성 정도를 상대적으로 표현하여 결합도가 강하다 또는 느슨하다라고 표현한다.
Movie가 PercentDiscountPolicy를 의존하면 비율로 할인을 적용한다는 정보를 알게 되지만 DiscountPolicy를 의존한다면 어떻게 할인하는지 모르게 된다.결합도를 느슨하게 하기 위해서는 클래스 안에 모든 구체 클래스에 대한 의존성을 제거해야 한다.
public class Movie {
...
private DiscountPolicy discountPolicy;
public Movie(String title, Duration runningTime, Money fee) {
...
this.discountPolicy = new AmountDiscountpolicy(...);
}
}
DiscountPolicy를 의존하고 있지만 구체 클래스인 AmountDiscountPolicy를 직접 생성해 대입하고 있다.이를 해결하려면 생성자를 통해 의존성 주입을 받는 방법이 있다.
public class Movie {
...
private DiscountPolicy discountPolicy;
public Movie(String title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) {
...
this.discountPolicy = discountPolicy;
}
}
Movie 생성자에 DiscountPolicy를 받음으로 의존 대상이 퍼블릭 인터페이스에 드러난다.new를 잘못 사용하면 결합도가 극단적으로 높아진다.
new 연산자를 사용하면 구체 클래스 이름을 직접 기술해야 한다.new 연산자는 생성하려는 구체 클래스 뿐만 아니라 어떤 인자를 이용해 생성자를 호출해야 하는지도 알아야 한다.Movie가 대부분 AmountDiscountPolicy와 협력하고 가끔씩만 PercentDiscountPolicy와 협력하는 경우
아래 코드로 이 문제를 해결할 수 있다.
public class Movie {
...
private DiscountPolicy discountPolicy;
public Movie(String title, Duration runningTime, Money fee) {
this(title, runningTime, fee, new AmountDiscountPolicy(...));
}
public Movie(String title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) {
...
this.discountPolicy = discountPolicy;
}
}
DiscountPolicy를 주입 받지 않는 경우 기본값을 설정하도록 구현DiscountPolicy 인스턴스로 의존성을 교체할 수 있다.의존성이 불편한 이유는 변경에 대한 영향을 암시하기 때문
public abstract class DiscountPolicy {
private List<DiscountCondition> conditions = new ArrayList<>();
}
List 인터페이스에 의존하는 것도 이 때문Movie에 할인 정책이 적용되지 않는 것이 기본 정책이라면 아래처럼 코드를 짤 수 있다.
public class Movie {
...
private DiscountPolicy discountPolicy;
public Movie(String title, Duration runningTime, Money fee) {
this(title, runningTime, fee, new NoneDiscountPolicy(...));
}
public Movie(String title, Duration runningTime, Money fee, DiscountPolicy discountPolicy) {
...
this.discountPolicy = discountPolicy;
}
public Movie claculateMovieFee(Screening screening) {
return fee.minus(discountPolicy.calculateDiscountAmount(screening));
}
}
NoneDiscountPolicy 덕에 if 문을 추가하지 않고도 할인 혜택을 제공하지 않는 영화를 구현할 수 있다.Movie를 만드려면 어떻게 해야할까?
DiscountPolicy와 협력하기 위해 List<DiscountPolicy>를 의존하는 방법이 있지만 이는 기존 협력 방식에 위배된다.OverlappedDiscountPolicy를 통해 기존 협력 방식을 유지한채 확장할 수 있다.
public class OverlappedDiscountPolicy extends DiscountPolicy {
private List<DiscountPolicy> discountPolicies = new ArrayList<>();
public OverlappedDiscountPolicy(DiscountPolicy ... discountPolicies) {
this.discountPolicies = Arrays.asList(discountPolicies);
}
@Override
protected Money getDiscountAmount(Screening screening) {
Money result = Money.ZERO;
for (DiscountPolicy each : discountPolicies) {
result = result.plus(each.calculateDiscountAmount(screening));
}
return result;
}
}
new Movie("아바타",
Duration.ofMinutes(120),
Money.wons(10000),
new AmountDiscountPolicy(Money.wons(800),
new SequenceCondition(1),
new SequenceCondition(10),
new PeriodCondition(DayOfWeek.MONDAY, LocalTime.of(10, 0), LocalTime.of(12, 0)),
new PeriodCondition(DayOfWeek.THURSDAY, LocalTime.of(10, 0), LocalTime.of(21, 0))));