Table of contents
Open Table of contents
원칙의 함정
디미터 법칙은 하나의 도트(.)를 강제하는 규칙이 아니다.
아래 코드가 디미터 법칙을 위반하는 코드일까?
IntStream.of(1, 15, 20, 3, 9)
.filter(number -> number > 10)
.distinct()
.count()
왜 위반하지 않는 코드일까?
=> 디비터 법칙은 결합도와 관련된 것이며, 이 결합도가 문제가 되는 것은 객체의 내부 구조가 외부로 노출되는 경우로 한정된다.
IntStream
의 내부 구조가 외부로 노출되지 않았다.
결합도와 응집도의 충돌
묻지 말고 시켜라와 디미터 법칙을 준수하는 것이 항상 긍정적인 결과로만 귀결되는 것은 아니다. 모든 상황에서 맹목적으로 위임 메서드를 추가하면 같은 퍼블릭 인터페이스 안에 어울리지 않는 오퍼레이션들이 공존하게 된다.
아래 코드는 얼핏 보기에는 Screening
의 내부 상태를 가져와서 사용하기 때문에 캡슐화를 위반하는 것으로 보일 수 있다.
public class PeriodCondition implements DiscountCondition {
public boolean isSatisfiedBy(Screening screening) {
return screening.getWhenScreened().getDayOfWeek().equals(dayOfWeek) &&
startTime.compareTo(screening.getWhenScreened().toLocalTime()) <= 0 &&
endTime.compareTo(screening.getWhenScreened().toLocalTime()) >= 0;
}
}
할인 여부를 판단하는 로직을 Screening
에 위임하면 어떻게 될까?
public class Screening {
public boolean isSatisfiedBy(PeriodCondition condition) {
return condition.isSatisfiedBy(this);
}
}
이렇게 하면 Screening
이 기간에 따른 할인 조건을 판단하는 책임을 떠안게 된다.
Screening
의 책임은 영화를 예매하는 것이다.
명령-쿼리 분리 원칙
어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈을 루틴(routine)이라고 한다. 루틴은 다시 프로시저(procedure) 와 함수(function) 로 나뉜다.
- 프로시저(명령): 정해진 절차에 따라 내부 상태를 변경하는 루틴의 한 종류, 부수효과를 발생시킬 수 있지만 값을 반환할 수 없다.
- 함수(쿼리): 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 한 종류, 값을 반환할 수 있지만 부수효과를 발생시킬 수 없다.
명령과 쿼리를 뒤섞으면 실행 결과를 예측하기 어렵다 다음 코드의 문제점을 살펴보고 개선해보자
public class Event {
private String subject;
private LocalDateTime from;
private Duration duration;
public boolean isSatisfied(RecurringSchedule schedule) {
if (from.getDayOfWeek() != schedule.getDayOfWeek() ||
!from.toLocalTime().equals(schedule.getFrom()) ||
!duration.equals(schedule.getDuration())) {
reschedule(schedule);
return false;
}
return true;
}
private void reschedule(RecurringSchedule schedule) {
from = LocalDateTime.of(from.toLocalDate().plusDays(dayDistance(schedule)), schedule.getFrom());
duration = schedule.getDuration();
}
private long dayDistance(RecurringSchedule schedule) {
return DayOfWeek.from(from.getDayOfWeek()).getValue() - schedule.getDayOfWeek().getValue();
}
}
isSatisfied
메서드는Event
가RecurringSchedule
에 만족하는지 여부를 판단한다. 이 메서드는 개념적으로 쿼리다.isSatisfied
조건에 부합하지 않을 경우from
과duration
을 변경한다. 이 메서드는 부수효과를 가지고 있으므로 명령이다.
명령과 쿼리를 명확하게 분리해보자
public class Event {
public boolean isSatisfied(RecurringSchedule schedule) {
if (from.getDayOfWeek() == schedule.getDayOfWeek() &&
from.toLocalTime().equals(schedule.getFrom()) &&
duration.equals(schedule.getDuration())) {
return true;
}
return false;
}
public void reschedule(RecurringSchedule schedule) {
from = LocalDateTime.of(from.toLocalDate().plusDays(dayDistance(schedule)), schedule.getFrom());
duration = schedule.getDuration();
}
}
명령-쿼리 분리와 참조 투명성
- 명령과 쿼리를 엄격하게 분류하면 객체의 부수효과를 제어하기가 수월해진다.
- 명령과 쿼리를 분리함으로써 명령형 언어의 틀 안에서 참조 투명성(referential transparency) 의 장점을 제한적이나마 누릴 수 있게 된다.
- 참조 투명성이란 어떤 표현식 e가 있을 때 e의 값으로 나타나는 모든 위치를 교체하더라도 결과가 달라지지 않는 특성
참조 투명성을 만족하는 식은 두 가지 장점을 제공한다.
- 모든 함수를 이미 알고 있는 하나의 결과값으로 대체할 수 있기 때문에 식을 쉽게 계산할 수 있다.
- 모든 곳에서 함수의 결괏값이 동일하기 때문에 식의 순서를 변경하더라도 각 식의 결과는 달라지지 않는다.