목차

오브젝트; 명령-쿼리 분리

🗓️

명령-쿼리 분리 원칙

  • 퍼블릭 인터페이스에 오퍼레이션을 정의할때 참고할 수 있는 지침을 제공.
  • 루틴 Routine : 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능
    • 프로시저 Procedure : 정해진 절차에 따라 내부의 상태를 변경하는 루틴의 종류. 부수효과를 발생시킴
    • 함수 Function : 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 종류. 부수효과가 없다.
  • 명령 Command : 객체의 상태를 변경하는 명령은 반환값을 가질 수 없다.
  • 쿼리 Query : 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.

질문이 답변을 수정해서는 안된다.

반복 일정의 명령과 쿼리 분리하기

public class Event {
    private String subject;
    private LocalDateTime from;
    private Duration duration;

    //.. constructor
}
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;
    }

    private void reschedule(RecurringSchedule schedule) {
        from = LocalDateTime.of(from.toLocalDate().plusDays(datsDistance(schedule)),
                                schedule.getFrom());
        duration = schedule.getDuration();
    }

    private long datsDistance(RecurringSchedule schedule) {
        return schedule.getDayOfWeek().getValue() - from.getDayOfWeek().getValue();
    }
}
  • isSatisfied() 에서 상태를 변경하고 있다. 즉, 부수효과를 발생시킨다.
public class Event {
    public boolean isSatisfied(RecurringSchedule schedule) {
        if (from.getDayOfWeek() != schedule.getDayOfWeek() ||
            !from.toLocalTime().equals(schedule.getFrom()) ||
            !duration.equals(schedule.getDuration()) {
            return false;
        }

        return true;
    }

    public void reschedule(RecurringSchedule schedule) {
        from = LocalDateTime.of(from.toLocalDate().plusDays(datsDistance(schedule)),
                                schedule.getFrom());
        duration = schedule.getDuration();
    }
}
  • 개선후
if (!event.isSatisfied(schedule)) {
    event.reschedule(schedule);
}
  • 이런 경우는 필요하다면 호출하는 쪽에서 결정을 해야 한다.

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

참조 투명성 Referential transparency

  • 어떤 표현식 e 가 있을 때 e 의 값으로 e 가 나타나는 모든 위치를 교체하더라도 결과가 달라지지 않는 특성
  • 컴퓨터와 수학을 나누는 가장 큰 특징은 부수효과의 존재유무다.
  • 프로그램에서 부수효과의 가장큰 문법 두가지는 대입문과 함수다. 수학은 변수 초기화 후 값 변경이 불가능하지만 프로그램은 가능하다

불변성 Immutability

  • 어떤 값이 변하지 않는 성질.
  • 부수효과가 발생하지 않는 것.

참조 투명성을 만족하는 경우..

모든 함수를 이미 알고 있는 하나의 결괏값으로 대체할 수 있기 때문에 식을 쉽게 계산할 수 있다.
모든 곳에서 함수의 결과값이 동일하기 때문에 식의 순서를 변경하더라도 각 식의 결과는 달라지지 않는다.

명령형 프로그래밍과 함수형 프로그래밍

부수효과를 기반으로 하는 프로그래밍 방식을 명령형 프로그래밍 Imperatice programming 이라고 부른다. 명령형 프로그래밍은 상태를 변경시키는 연산들을 적절한 순서대로 나열함으로써 프로그램을 작성한다. 대부분의 객체지향 프로그래밍 언어들은 메시지에 의한 객체 상태 변경에 집중하기 때문에 명령형 프로그래밍 언어로 분류된다. 사실 프로그래밍 언어가 생겨난 이후 주류라고 불리던 대부분의 프로그래밍 언어는 명령형 프로그래밍 언어의 범주에 속한다고 봐도 무방하다.
최근 들어 주목받고 있는 함수형 프로그래밍 Functional programming 은 부수효과가 존재하지 않는 수학적인 함수에 기반한다. 따라서 함수형 프로그래밍에서는 참조 투명성의 장점을 극대화할 수 있으며 명령형 프로그래밍에 비해 프로그램의 실행 결과를 이해하고 예측하기 더 쉽다. 또한 하드웨어의 발달로 병렬 처리가 붕요해진 최근에는 함수형 프로그래밍의 인기가 상승하고 있으며 다양한 객체지향 언어들이 함수형 프로그래밍 패러다임을 접목시키고 있는 추세다.

책임에 초점을 맞춰라

  • 디미터 법칙 : 협력이라는 컨텍스트 안에서 객체보다 메시지를 먼저 결정하면 두 객체 사이의 구조적인 결합도를 낮출 수 있다. 수신할 객체를 알지 못한 상태에서 메시지를 먼저 선택하기 때문에 객체의 내부 구조에 대해 고민할 필요가 없어진다. 따라서 메시지가 객체를 선택하게 함으로써 의도적인 디미터 법칙을 위반할 위험을 최소화할 수 있다.
  • 묻지 말고 시켜라 : 메시지를 먼저 선택하면 묻지 말고 시켜라 스타일에 따라 협력을 구조화하게 된다. 클라이언트의 관점에서 메시지를 선택하기 때문에 필요한 정보를 물을 필요 없이 원하는 것을 표현한 메시지를 전송하면 된다.
  • 의도를 드러내는 인터페이스 : 메시지를 먼저 선택한다는 것은 메시지를 전송하는 클라이언트의 관점에서 메시지의 이름을 정한다는 것이다. 당연히 그 이름에는 클라이언트가 무엇을 원하는지, 그 의도가 분명하게 드러날 수 밖에 없다.
  • 명령-쿼리 분리 원칙 : 메시지를 먼저 선택한다는 것은 협력이라는 문맥 안에서 객체의 인터페이스에 관해 고민한다는 것을 의미한다. 객체가 단순히 어떤 일을 해야 하는지뿐만 아니라 협력 속에서 객체의 상태를 예측하고 이해하기 쉽게 만들기 위한 방법에 관해 고민하게 된다. 따라서 예측 가능한 협력을 만들기 위해 명령과 쿼리를 분리하게 될 것이다.