Tell and Not Ask와 Command Query Separation 중에서 선택하는 방법은 무엇입니까?


25

Tell Do n't Ask 원칙 은 다음과 같이 말합니다.

당신이 원하는 것을 대상에게 말하려고 노력해야한다. 그들에게 자신의 상태에 대해 질문하지 말고 결정을 한 다음 무엇을해야하는지 이야기하십시오.

문제는 호출자로서 호출 된 객체의 상태를 기반으로 결정을 내려서 객체의 상태를 변경해서는 안된다는 것입니다. 당신이 구현하고있는 논리는 아마도 당신의 책임이 아닌 객체의 책임 일 것입니다. 객체 외부에서 결정을 내리려면 캡슐화를 위반합니다.

"Tell, Do n't Ask"의 간단한 예 는 다음과 같습니다.

Widget w = ...;
if (w.getParent() != null) {
  Panel parent = w.getParent();
  parent.remove(w);
}

말해 버전은 ...

Widget w = ...;
w.removeFromParent();

그러나 removeFromParent 메소드의 결과를 알아야하는 경우 어떻게해야합니까? 첫 번째 반응은 부모가 제거되었는지 여부를 나타내는 부울을 반환하도록 removeFromParent를 변경하는 것입니다.

그러나 나는 이것을하지 말라고 명령 쿼리 분리 패턴보았습니다 .

모든 메소드는 조치를 수행하는 명령이거나 호출자에게 데이터를 리턴하는 쿼리 여야하지만 둘다는 아니라고 명시합니다. 다시 말해, 질문을해도 대답이 바뀌지 않아야합니다. 보다 공식적으로, 메소드는 참조 적으로 투명하고 부작용이없는 경우에만 값을 리턴해야합니다.

이 두 가지가 실제로 서로 상충되며 두 가지 중에서 어떻게 선택합니까? Pragmatic Programmer 또는 Bertrand Meyer와 함께 가나 요?


1
부울로 무엇을 하시겠습니까?
David

1
사용성에 균형을
맞추지

다시 부울 ... 이것은 피기하기 쉽지만 아래의 쓰기 작업과 비슷하지만 목표는 작업 상태가되는 것입니다.
Dakotah North

2
내 요점은 당신이 "무언가를 돌려주는 것"에 초점을 맞추지 말고 당신이하고 싶은 것에 더 집중해야한다는 것입니다. 다른 의견에서 말했듯이 실패에 초점을 맞춘 경우 예외를 사용하고 작업이 완료되었을 때 작업을 수행하려는 경우 콜백 또는 이벤트를 사용하십시오. 발생한 상황을 기록하려면 위젯 책임입니다. 왜 당신의 요구를 요구하고 있습니다. 무언가를 반환 해야하는 구체적인 예를 찾을 수 없다면 둘 중 하나를 선택할 필요가 없습니다.
David

1
명령 쿼리 분리는 패턴이 아니라 원칙입니다. 패턴은 문제를 해결하는 데 사용할 수있는 것입니다. 원칙은 문제를 일으키지 않기 위해 따르는 것입니다.
candied_orange

답변:


25

실제로 예제 문제는 이미 분해 부족을 나타냅니다.

약간 변경해 보자.

Book b = ...;
if (b.getShelf() != null) 
    b.getShelf().remove(b);

이것은 실제로 다르지 않지만 결함을 더 분명하게 만듭니다. 왜 책이 선반에 대해 알고 있습니까? 간단히 말하면 안됩니다. 그것은 선반에 책의 의존성을 소개하고 (이치에 맞지 않습니다) 순환 참조를 만듭니다. 모두 나쁘다.

마찬가지로 위젯이 부모를 알 필요는 없습니다. "좋아요.하지만 위젯 자체를 올바르게 레이아웃하려면 부모가 필요합니다." 그리고 위젯 아래에서 위젯은 부모를 알고 있으며, 그들에 기초하여 자신의 메트릭을 계산하기 위해 메트릭을 요구 합니다. 부모는 필요한 모든 정보를 인수로 전달하여 모든 자식에게 렌더링하도록 지시해야합니다. 이렇게하면 두 부모에서 동일한 위젯을 쉽게 가질 수 있습니다 (실제로 의미가 있든 없든).

책 예제로 돌아가려면-사서를 입력하십시오.

Librarian l = ...;
Book b = ...;
l.findShelf(b).remove(b);

우리가하지 않는 것을 양해 해 주시기 바랍니다 물어 더 이상 의미에서 말해 요구하지 않습니다 .

첫 번째 버전에서 선반은 책의 소유물이었으며 요청했습니다. 두 번째 버전에서는이 책에 대해 그러한 가정을하지 않습니다. 우리가 아는 것은 사서가 책이 들어있는 선반을 말해 줄 수 있다는 것입니다. 아마도 그는 그렇게하기 위해 일종의 조회 테이블에 의존하지만, 우리는 확실하지 않습니다 (그는 모든 선반에있는 모든 책을 반복 할 수도 있음). 실제로 우리는 알고 싶지 않습니다 .
우리는 사서의 반응이 그것의 상태 또는 그것이 의존 할 수있는 다른 물건들의 반응에 연결되어 있는지의 여부에 의존하지 않는다. 우리는 사서에게 선반을 찾도록 지시합니다.
첫 번째 시나리오에서는 서적 상태의 일부로 서적과 서가 사이의 관계를 직접 인코딩하고이 상태를 직접 검색했습니다. 우리는 또한 책에 의해 주어진 선반에 책이 포함되어 있다고 가정하기 때문에 매우 약합니다 remove.

사서의 도입으로 우리는이 관계를 개별적으로 모델링하여 관심사 분리를 달성했습니다.


나는 이것이 매우 유효한 포인트라고 생각하지만 전혀 질문에 대답하지 않습니다. 귀하의 버전에서 질문은 Shelf 클래스의 remove (Book b) 메소드가 반환 값을 가져야합니까?
scarfridge

1
@scarfridge : 결론에서, 질문하지 말고 우려를 적절히 분리 한 것을 확인하십시오. 당신이 그것에 대해 생각한다면, 나는 질문에 대답합니다. 적어도 나는 그렇게 생각할 것이다;)
back2dos

1
@scarfridge 반환 값 대신 실패시 호출되는 함수 / 대리자를 전달할 수 있습니다. 성공하면 끝났습니까?
블레스 야후

2
@BlessYahu 그것은 저에게 과장된 것 같습니다. 나는 개인적으로 커맨드 쿼리 분리가 실제 (멀티 스레드) 세계에서 더 이상적이라고 생각합니다. IMHO 부작용이있는 메소드는 메소드의 이름이 명확하게 나타내는 한 객체의 상태를 변경하는 한 값을 반환하는 것이 좋습니다. 스택의 pop () 메소드를 고려하십시오. 그러나 쿼리 이름이있는 메소드에는 부작용이 없어야합니다.
scarfridge

13

결과를 알아야한다면 그렇게해야합니다. 그것이 당신의 요구 사항입니다.

"방법은 참조 적으로 투명하고 부작용이없는 경우에만 값을 반환해야합니다"라는 문구는 따라야 할 좋은 지침입니다 (특히 동시성 또는 기타 이유로 함수형 스타일로 프로그램을 작성하는 경우 ). 절대가 아닙니다.

예를 들어 파일 쓰기 작업의 결과 (true 또는 false)를 알아야합니다. 이는 반환하지만 항상 부작용을 일으키는 메서드의 예입니다 . 그 주위에 방법이 없습니다.

명령 / 쿼리 분리를 수행하려면 한 가지 방법으로 파일 작업을 수행 한 다음 다른 방법으로 결과를 확인해야합니다. 다른 방법 은 결과를 초래 한 방법과 결과를 분리 하기 때문에 바람직하지 않은 방법입니다. 파일 호출과 상태 확인 사이에 객체 상태에 문제가 발생하면 어떻게합니까?

결론은 다음과 같습니다. 메소드 호출 결과를 사용 하여 프로그램의 다른 곳에서 결정을 내리는 경우 Tell Do n't Ask를 위반하지 않습니다. 반면에 해당 객체에 대한 메서드 호출을 기반으로 객체에 대한 결정을 내릴 경우 캡슐화를 유지하기 위해 해당 결정을 객체 자체로 이동 해야합니다 .

이것은 명령 쿼리 분리와 전혀 모순되지 않습니다. 사실, 그것은 더 이상 상태 목적을 위해 외부 방법을 노출시킬 필요가 없기 때문에 그것을 강화합니다.


1
예를 들어, "쓰기"작업이 실패하면 예외가 발생해야하므로 "상태 가져 오기"방법이 필요하지 않다고 주장 할 수 있습니다.
David

@David : 그런 다음 OP의 예제로 돌아가서 변경이 실제로 발생하면 true를 반환하고 그렇지 않으면 false를 반환합니다. 나는 당신이 거기에 예외를 던지고 싶어 의심합니다.
Robert Harvey

OP의 예조차도 나에게 적합하지 않습니다. 제거가 불가능한 경우 알림을 받으려면 예외를 사용하거나 위젯이 실제로 제거 될 때 알림을 받으려면 이벤트 또는 콜백 메커니즘을 사용할 수 있습니다. 나는 "명령 쿼리 분리 패턴"을 존중하고 싶지 않은 실제 예제를 보지 못했습니다
David

@David : 나는 명확히하기 위해 대답을 편집했습니다.
Robert Harvey

너무 지적 하지 않아도 되지만 명령-쿼리 분리의 기본 개념은 파일을 작성하기 위해 명령을 발행하는 사람 은 결과에 신경 쓰지 않는다는 것입니다. 적어도 특정 의미는 아닙니다 (사용자는 나중에 모든 최근 파일 작업 목록을 가져 오지만 초기 명령과는 상관이 없습니다. 결과에 대한 관심 여부와 상관없이 명령이 완료된 후 조치를 취해야하는 경우이를 수행하는 올바른 방법 은 원래 명령 및 / 또는 해당 명령에 대한 세부 사항을 포함 하는 이벤트 를 사용하는 비동기식 프로그래밍 모델을 사용 하는 것입니다. 결과.
Aaronaught

2

나는 당신이 당신의 초기 본능에 가야한다고 생각합니다. 때로는 디자인이 의도적으로 위험해야 개체와 통신 할 때 즉시 복잡성이 발생하여 개체 자체를 처리하고 모든 까다로운 세부 사항을 숨기고 깨끗하게하기 어렵게 만드는 대신 강제로 직접 처리 할 수 ​​있습니다. 기본 가정을 변경하십시오.

객체가 동등한 기능 openclose동작 을 구현 하면 간단한 원자 작업이라고 생각되는 무언가에 대한 부울 리턴 값을 볼 때 다루는 내용을 즉시 이해하기 시작 하면 가라 앉는 느낌이들 것입니다.

나중에 다루는 방법은 당신의 일입니다. 위의 위젯 유형 인 위의 추상화를 만들 수 removeFromParent()있지만 항상 낮은 수준의 폴 백이 있어야합니다. 그렇지 않으면 조기에 가정했을 수 있습니다.

모든 것을 단순하게 만들려고하지 마십시오. 우아하고 결백 한 것처럼 보이는 것에 의존 할 때 실망스러운 것은 없습니다. 최악의 순간에 그것이 진정한 공포임을 깨닫기 위해서입니다.


2

설명하는 것은 명령 쿼리 분리 원칙에 대한 "예외"입니다.

이 기사에서 Martin Fowler 는 그러한 예외를 어떻게 위협 할 수 있는지 설명합니다.

Meyer는 명령 쿼리 분리를 절대적으로 사용하지만 예외가 있습니다. 스택을 제거하는 것은 상태를 수정하는 쿼리의 좋은 예입니다. 메이어는이 방법을 피할 수 있다고 올바르게 말하지만 유용한 관용구입니다. 그래서 나는 할 수있을 때이 원칙을 따르는 것을 선호하지만 나는 팝을 얻기 위해 그것을 깨뜨릴 준비가되어 있습니다.

귀하의 예에서 나는 동일한 예외를 고려할 것입니다.


0

명령 쿼리 분리는 놀랍게도 오해하기 쉽습니다.

내 시스템에 말하면 쿼리에서 값을 반환하는 명령이 있으며 "Ha! you 're wrong!" 당신은 총을 점프하고 있습니다.

아니요, 금지 된 것은 아닙니다.

금지 된 것은 해당 명령이 해당 쿼리를 만드는 유일한 방법 일 때입니다. 아니요. 질문을하기 위해 상태를 변경할 필요는 없습니다. 그것은 내가 상태를 바꿀 때마다 눈을 감아 야한다는 것을 의미하지는 않습니다.

어쨌든 다시 열어 놓을 것이기 때문에 중요하지 않습니다. 이 과잉 반응의 유일한 장점은 디자이너가 게으르지 않고 상태를 변경하지 않는 쿼리를 포함하는 것을 잊어 버리는 것입니다.

진정한 의미는 너무 미묘하여 게으른 사람들이 세터가 공허를 반환해야한다고 말하는 것이 더 쉽습니다. 이렇게하면 실제 규칙을 위반하지 않는 것이 더 쉬워 지지만 실제 고통이 될 수있는 과도한 반응입니다.

유창한 인터페이스와 iDSL은 이러한 과잉 반응을 항상 "위반"합니다. 과도하게 반응하면 많은 힘이 무시됩니다.

명령이 한 가지만 수행하고 쿼리는 한 가지만 수행해야한다고 주장 할 수 있습니다. 그리고 많은 경우에 그것은 좋은 지적입니다. 명령을 따라야하는 쿼리가 하나만 있다고 누가 말합니까? 괜찮지 만 명령 쿼리 분리가 아닙니다. 이것이 단일 책임 원칙입니다.

이런 식으로 보면 스택 팝은 기괴한 예외가 아닙니다. 단일 책임입니다. 팝은 피킹을 잊어 버린 경우에만 명령 쿼리 분리를 위반합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.