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))));