condition.isSatisfiedBy(screening)
에서 수신자.오퍼레이션명(인자)
로 볼 수 있다.condition
)에게 메시지를 전송하더라도 객체 타입에 따라 실행되는 메서드는 달라질 수 있다..
)만 사용하라”라는 말로 요약되기도 한다.screening.getMovie().getDiscountConditions()
메서드가 작업을 어떻게 수행하는지 나타내면 문제가 발생한다.
public class PeriodCondition {
public boolean isSatisfiedByPeriod(Screening screening) {...}
}
public class SequenceCondition {
public boolean isSatisfiedBySequence(Screening screening) {...}
}
‘무엇’을 하는지 드러내는 메서드 이름은 코드를 이해하기 쉽고 유연하게 만든다.
public interface DiscountCondition {
boolean isSatisfiedBy(Screening screening);
}
IntStream.of(1, 15, 20, 3, 9).filter(x → x > 10).distinct().count();
Stream
을 다른 Stream
으로 변환할 뿐 객체의 캡슐은 유지되기 때문PeriodCondition
클래스를 살펴보자
public class PeriodCondition implements DiscountCondition {
public boolean isSatisfiedBy(Screening screening) {
return screening.getStartTime().getDayOfWeek().equals(dayOfWeek) &&
startTime.compareTo(screening.getStartTime().toLocalTime()) <= 0 &&
endTime.compareTo(screening.getStartTime().toLocalTime()) >= 0;
}
}
Screening
내부 상태를 가져와 사용하기에 캡슐화 위반처럼 보인다.따라서 Screening.isDiscountable()
메서드를 만들고 위임한다면 ‘묻지 말고 시켜라’ 원칙을 준수할 수 있을 것이다.
return screening.isDiscountable(dayOfWeek, startTime, endTime);
Screening
이 할인 조건 판단을 책임져야 하는지 의문을 가져야 한다.Screening
이 PeriodCondition
인스턴스 변수를 인자로 받기에 결합이 높아진다.Screening
의 응집을 높이고 결합을 낮추는 것이 원칙을 지키는 것보다 더 좋다고 판단될 수 있다.가끔 묻는 것 외에 다른 방법이 존재하지 않는 경우도 존재한다.
for (Movie each : movies) {
total += each.getFee();
}
이벤트
는 특정 일자에 발생하는 사건을 의미
public class Event {
private String subject; // 이벤트 주제
private LocalDateTime from; // 시작 일시
private Duration duration; // 소요 시간
public Event(String subject, LocalDateTime from, Duration duration) {
this.subject = subject;
this.from = from;
this.duration = duration;
}
// ...
}
반복 일정
은 일주일 단위로 돌아오는 일정 시간 간격에 발생하는 사건 전체를 포괄적으로 지칭하는 용어
public class RecurringSchedule {
private String subject; // 일정 주제
private DayOfWeek dayOfWeek; // 반복될 요일
private LocalTime from; // 시작 시간
private Duration duration; // 기간
public RecurringSchedule(String subject, DayOfWeek dayOfWeek,
LocalTime from, Duration duration) {
this.subject = subject;
this.dayOfWeek = dayOfWeek;
this.from = from;
this.duration = duration;
}
}
Event
에는 현재 이벤트가 RecurringSchedule
이 정의한 반복 일정 조건을 만족하는지 검사하는 isSatisFied
메서드를 제공한다.
public class Event {
// ...
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; // 이벤트가 일정 조건을 만족하는 경우
}
// ...
}
위 isSatisfied
메서드는 처음에 false
를 반환하더라도 또 호출하면 true
를 반환한다.
RecurringSchedule schedule = new RecurringSchedule("회의", DayOfWeek.WEDNESDAY, ...);
Event meeting = new Event("회의", LocalDateTime.of(2019, 5, 9, 10, 30), Duration.ofMinutes(30));
assert meeting.isSatisfied(schedule) == false;
assert meeting.isSatisfied(schedule) == true;
isSatisfied
메서드가 false
를 반환해야 하는 경우 reschedule
이라는 Event
객체를 수정하는 메서드를 호출하기 때문이다.isSatisfied
가 명령과 쿼리 두 역할을 동시에 수행했었기에 찾기 힘든 버그가 만들어진다.
isSatisfied
메서드는 Event
가 RecurringSchedule
의 조건에 부합하는지를 판단한 후 부합 여부에 따라 boolean
을 반환하므로 개념적으로 쿼리다.isSatisfied
메서드는 Event
가 RecurringSchedule
의 조건에 부합하지 않을 경우 Event
를 조건에 부합하도록 변경하기에 실제로는 부수효과를 가지는 명령이다.명령과 쿼리를 분리하면 인터페이스를 훑어보는 것만으로도 쿼리인지 명령인지 한눈에 알 수 있다.
public Event {
public boolean isSatisfied(RecurringSchedule schedule) { ... }
public void reschedule(RecurringSchedule schedule) { ... }
}
코드는 예측 가능하고 이해하기 쉬우며 디버깅이 용이한 동시에 유지보수가 수월해질 것이다.
“어떤 표현식 e가 있을 때 e의 값으로 e가 나타내는 모든 위치를 교체하더라도 결과가 달라지지 않는 특성”
f(n)이 존재할 때 f(1) = 3이라고 가정
f(1) + f(1) = 6
f(1) * 2 = 6
f(1) - 1 = 2
f(1)을 실제 결과값인 3으로 바꿔도 식의 결과는 변하지 않는다.
3 + 3 = 6
3 * 2 = 6
3 - 1 = 2
Event
의 reschedule
메서드를 호출하지 않는 한 isSatisfied
메서드를 어떤 순서로 몇 번 호출하든 결과는 동일하다.부수 효과를 기반으로 하는 프로그래밍을 ‘명령형 프로그래밍’이라 부른다. 최근 주목 받는 ‘함수형 프로그래밍’은 부수효과가 존재하지 않는 수학적 함수에 기반한다. 함수형 프로그래밍에서는 참조 투명성을 극대화할 수 있으며 명령형 프로그래밍에 비해 실행 결과를 이해하고 예측하기 쉽다.