Skip to content

책임 할당하기

Published: at 오후 01:47

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

명령과 쿼리를 명확하게 분리해보자

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

명령-쿼리 분리와 참조 투명성

참조 투명성을 만족하는 식은 두 가지 장점을 제공한다.