본문 바로가기

책/오브젝트 (완)

06 메시지와 인터페이스

https://goto-pangyo.tistory.com/230

 

05 책임과 메시지

4장에 이어 5장에서는 설계에 도움이 될 법한 실질적인 조언을 해주고 있습니다. 저자는 '메시지'의 역할 및 중요성을 이야기하고 이를 설계와 코드에 어떻게 녹이며, 인터페이스의 역할에 대해

goto-pangyo.tistory.com

이미 비슷한 내용으로 포스팅을 했었습니다. 그러나 오브젝트에서 언급하는 메시지는 조금더 구체적이고 또 실제 개발을 할 때 도움이 되는 내용들이였다는 느낌을 받았습니다. 추가로 "6장 메시지와 인터페이스"는 좋은 내용들이 풍부하지만 "객체지향의 사실과 오해"에서 다루었던 내용은 간략하게 설명하거나 건너뛰며 글을 작성하고자 합니다.


훌륭한 객체지향 코드를 얻기 위해서는 클래스가 아니라 객체를 지향해야 한다. 좀 더 정확하게 말해서 협력 안에서 객체가 수행하는 책임에 초점을 맞춰야 한다. 여기서 중요한 것은 책임이 객체가 수신할 수 있는 메시지의 기반이 된다는 것이다.

반복해서 말하는 내용입니다. 좋은 설계의 기반은 '메시지'라는 것, 그리고 클래스는 객체를 구현하기 위한 수단에 불과하다는 것입니다.

 

01 협력과 메시지

클라이언트-서버 모델

메시지를 매개로 하는 요청과 응답의 조합이 두 객체 사이의 협력을 구성한다.
...
두 객체 사이의 협력 관계를 설명하기 위해 사용하는 전통적인 메타포는 클라이언트-서버(Client-Server)모델 이다.
...
객체가 독립적으로 수행할 수 있는 것보다 더 큰 책임을 수행하기 위해서는 다른 객체와 협력해야 한다는 거이다. 그리고 두 객체 사이의 협력을 가능하게 해주는 매개체가 바로 메시지라는 것이다.

객체간에도 클라이언트-서버 모델을 따른다는 것을 처음 알았습니다. 그 외의 내용은 이미 반복해서 보고 듣고 공부한내용입니다.

 

메시지와 메서드

이처럼 메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저를 메서드라고 부른다. 중요한 것은 코드 상에서 동일한 이름의 변수에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다는 것이다.
...
메시지 전송자와 메시지 수신자는 서로에 대한 상세한 정보를 알지 못한 채 단지 메시지라는 얇고 가는 끈을 통해 연결된다. 실행 시점에 메시지와 메서드를 바인딩하는 메커니즘은 두 객체 사이의 결합도를 낮춤을써 유연하고 확장 가능한 코드를 작성할 수 있게 만든다.

첫번째 문단의 마지막 문장은 추상화로 인한 결과를 말합니다. 또한 객체간의 결합도를 메시지 간의 결합해 결과적으로 결합도를 낮출 수 있습니다.

 

퍼블릭 인터페이스와 오퍼레이션

객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스라고 부른다.
...
퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션이라고 부른다.
...
실제로 실행되는 코드는 메서드라고 부른다.
...
인터페이스와 메시지의 관점에서 보면 '메서드 호출'보다는 '오퍼레이션 호출'이라는 용어를 사용하는 것이 더 적절하다.

여기서 말하는 오퍼레이션은 구체적으로 예시를 들자면 인터페이스에 정의된 개별적인 메소드를 말하고 인터페이스는 이러한 오퍼레이션의 집합이라고 생각하면 될듯합니다.

 

시그니처

오퍼레이션(또는 메소드)의 이름과 파라미터 목록을 합쳐 시그니쳐(signature)라고 부른다.
...
오퍼레이션의 관점에서 다형성이란 동일한 오퍼레이션 호출에 대해 서로 다른 메서드들이 실행되는 것이라고 정의할 수 있다.
...
메시지가 객체의 품질을 결정한다고 할 수 있다.

평소에도 메서드 대신 시그니쳐라는 말을 종종 들었습니다. 그리고 시그니쳐는 메서드의 이름 그리고 인자, 반환값을 합쳐 말하는 것이라 생각을 했었습니다. 하지만 정확한 시그니쳐의 정의는 반환값을 제외한 오퍼레이션의 이름 그리고 파라미터의 이름을 합친것을 의미합니다.

02 인터페이스와 설계 품질

퍼블릭 인터페이스의 품질에 영향을 미치는 다음과 같은 원칙과 기법에 관해 살펴 보겠다.
1. 디미터의 법칙
2. 묻지말고 시켜라
3. 의도를 드러내는 인터페이스
4. 명령-쿼리 분리

위의 4가지만 지켜도 좋은 품질의 설계가 가능하다 생각합니다.

 

디미터의 법칙

이처럼 협력하는 개개체의 내부 구조에 대한 결합을 인해 발생하는 설계 문제를 해결하기 위해 제안된 원칙이 바로 디미터 법칙(Law of Demeter)이다. 디미터 법치을 간단하게 요약하면 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하라는 것이다.
...
디미터 법칙을 따르기 위해서는 클래스가 특정한 조건을 만족하는 대상에게만 멧지ㅣ를 전송하도록 프로그래밍해야 한다. 모든 크래스 C와 C에 구현된 모든 메서드 M이 메시지를 전송할 수 있는 모든 객체는 다음에 서술된 클래스의 인스턴스여야 한다. 이때 M에 의해 생성된 객체나 M이 호출하는 메서드에 의해 생성된 객체, 전역 변수로 선언된 객체는 모두 M의 인자로 간주한다.
- M의 인자로 전달된 클래스
- C의 인스턴스 변수의 클래스
...
메세지 전송자가 수신자의 내부 구조에 대해 물어보고 반환받은 요소에 대해 연쇄적으로 메시지를 전송한다. 흔히 이와 같은 코드를 기차 충돌이라고 부른다.
...
디미터 법칙은 자기 자신을 책임지는 자율적인 존재여야 한다는 사실을 강조한다.
...
하지만 무비찬적으로 디미터 법칙을 수용하면 퍼블릭 인터페이스 관점에서 객체의 응집도가 낮아질 수도 있다.

이는 디미터 법칙을 설명합니다. 즉 다른 객체의 상태를 물어 직접 로직을 처리하지 않고 해당 로직을 해당 객체에 위임하는 것입니다. 또한 이는 어디까지나 원칙이고 법칙이 아니므로 언제든 트레이드 오프의 대상이 될 수 있습니다.

 

묻지 말고 시켜라

디미터 법칙은 훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야 한다는 사실을 강조한다. 묻지 말고 시켜라(Tell, Don't Ask)는 이런 스타일의 메시지 작성을 장려하는 원칙을 가리키는 용어다.
...
내부의 상태를 묻는 오퍼레이션을 인터페이스에 포함시키고 있다면 더 나은 방법은 없는지 고민해 보라
...
상태를 묻는 오퍼레이션을 행동을 요청하는 오퍼레이션으로 대체함으로써 인터페이스를 향상시켜라. 협력을 설계하고 객체가 수신할 메시지를 결정하는 매 순간 묻지 말고 시켜라 원칙과 디미터 법칙을 머릿속에 떠올리는 것은 퍼블릭 인터페이스의 품질을 향살시킬 수 있는 좋은 습관이다.
...
인터페이스는 객체가 어떻게 하는지가 아니라 무엇을 하는지를 서술해야 한다.

내부의 상태를 가지고 로직을 작성하지 말고 또한 어떻게가 아닌 무엇을 시킬지 고민하는 것이 좋은 설계로 가는 첫 발걸음이라 생각합니다.

 

의도를 드러내는 인터페이스

무엇을 하는지를 드러내는 이름은 코드를 읽고 이해하기 쉽게 만들뿐만 아니라 유연한 코드를 낳는 지름길이다.
...
무엇을 하는지 드러내도록 메서드의 이름을 짓기 위해서는 객체가 협력 안에서 수행해야 하는 책임에 관해 고민해야 한다. 이것은 외부의 객체가 메시지를 전송하는 목적을 먼저 생각하도록 만들며, 결과적으로 협력하는 클라이언트의 의도에 부합하도록 메서드의 이름을 짓게 된다.
...
메서드가 어떻게 수행하느냔가 아니라 무엇을 하느냐에 초점을 맞추면 클라이언트의 관점에서 동일한 작업을 수행하는 메서드들을 하나의 타입 계층으로 묶을 수 있는 가능성이커진다. 그 결과, 다양한 타입의 객체가 참여할 수 있는 유연한 협력을 얻게 되는 것이다.
...
어떻게 하느냐가 아니라 무엇을 하느냐에 따라 메서드의 이름을 짓는 패턴을 의도를 드러내는 선택자라고 부른다.
...
수행방법에 관해서는 언급하지 말고 결과와 목적만을 포함하도록 클래스와 오퍼레이션의 이름을 부여하라. 이렇게 하면 클라이언트 개발자가 내부를 이해해야 할 필요성이 풀어든다.

어떻게 보면 클린코드에서 주장하는 명확한 이름 짓기와 비슷한 성격이라 생각이 듭니다. 그리도 주의 해야 할 점은 너무 구체적이지도 그렇다고 너무 추상적이지도 않은 이름을 짓는 것이 중요하다 생각합니다.

 

함께 모으기

근본적으로 디미터 법칙을 위반하는 설계는 인터페이스와 구현의 분리 원칙을 위반한다.
...
따라서 디미터 법칙을 위반한다는 것은 클라이언트에게 구현을 노출한다는 것을 의미하며, 그 결과 작은 요구사항 변경에도 쉽게 무너지는 불안정한 코드를 얻게된다.

디미터의 법칙을 위반했을때 얻게 되는 단점에 대해 언급하고 있다고 생각합니다. 즉 다른 객체의 상태를 직접 다룰 경우 불안정한 코드를 양산한다고 저자는 말을 하고 있습니다.

03 원칙의 함점

디미터 법칙은 하나의 도트(.)를 강제하는 규칙이 아니다.

디미터 법칙은 결합도와 관련된 것이며, 이 결합도가 문제가 되는 것은 객체의 내부 구조가 외부로 노출되는 경우로 한정된다.
...
하나 이상의 도트(.)를 사용하는 모든 케이스가 디미터 법칙 위반인 것은 아니다. 기차 충돌처럼보이는 코드라도 객체의 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 그것은 디미터 법칙을 준수한 것이다.

예를 들어 도트가 여러개 있다 하더라고 반환되는 객체가 이미 알고 있던 객체라면 이는 디미터 법칙을 위반하는 것이 아닙니다.

 

결합도와 응집도의 충돌

일반적으로 어떤 객체의 상태를 물어본 후 반환된 상태를 기반으로 결정을 내리고 그 결정에 따라 객체의 상태를 변경하는 코드는 묻지 말고 시켜라 스타일로 변경해야 한다.
...
묻지 말고 시켜라와 디미터 법칙을 준수하는 것이 항상 긍정적인 결과로만 귀결되는 것은 아니다. 모든 상황에서 맹목적으로 위임 메서드를 추가하며 같은 퍼블릭 인터페이스 안에 어울리지 않는 오퍼레이션들이 공존하게 된다. 결과적으로 객체는 상관 없는 책임들을 한꺼번에 떠안게 되기 때문에 결과적으로 응집도가 낮아진다.
...
객체는 내부 구조를 숨겨야 하므로 디미터 법칙을 따르는 것이 좋지만 자료 구조라면 당연히 내부를 노출해야 하므로 디미터 법칙을 적용할 필요가 없다.

디미터는 항상 무조건적으로 지켜야 하는 교리같은게 아닙니다. 그렇기에 때로는 디미터 법칙을 위반하더라도 결합도를 낮추고 응집도를 높일 수 있다면 해당 선택을 고려해봐야 합니다.

04 명령-쿼리 분리 원칙

어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈을 루틴이라고 부른다. 루틴은 다시 프로시저와 함수로 구분할 수 있다.
- 프로시저는 부수효과를 발생시킬 수 있지만 값을 반환할 수 없다.
- 함수는 값을 반환할 수 있지만 부수효과를 발생시킬 수 없다.
...
어떤 오퍼레이션도 명령인 동시에 쿼리여서는안된다. 따라서 명령과 쿼리를 분리하기 위해서는 다음의 두 가지 규칙을 준수해야 한다.
- 객체의 상태를 변경하는 명령은 반환값을 가질 수 없다.
- 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.

이것은 CQS 패턴이라고도 합니다. 이 패턴의 요지는 명령은 반환값을 가지되 상태에 대해 건드려서는 안되고 쿼리는 반환값이 없되 상태를 변경할 수 있다는 것입니다.

 

명령과 쿼리를 뒤섞이면 실행 결과를 예측하기가 어랴워질 수 있다.

개발자들은 코드 전부를 이해하기 어려운 부분이 있고 이를 해결하기 위한 방법중 하나로 CQS패턴이 나왔다고 생각합니다.

 

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

명령과 쿼리를 엄격하게 분류하면 객체의 부수효과를 제어하기가 수월해진다. 쿼리는 객체의 상태를 변경하지 않기 때문에 몇 번이고 반복적으로 호출하더라도 상관이 없다. 명령이 개입하지 않는 한 쿼리의 값은 변경되지 않기 때문에 쿼리의 결과를 예측하기 쉬워진다. 또한 쿼리들의 순서를 자유롭게 변경할 수도 있다.
...
명령과 쿼리를 분리함으로써 명령형 언어의 틀 안에서 참조 투명성의 장점을 제한적이나마 누릴 수 있게 된다.
...
참조 투명성이란 "어떤 표현식 e가 있을때 e의 값으로 e가 나타나는 모든 위치를 교체하더라도 결과가 달라지지 않는 특성"을 의미한다.
...
어떤 값이 변하지 않는 성질을 불변성이라고 부른다. 사실 어떤 값이 불변한다는 말은 부수효과가 발생하지 않는다는 말과 동일하다.
...
- 모든 함수를 이미 알고 있는 하나의 결괏값으로 대체할 수 있기 때문에 식을 쉽게 계산할 수 있다.
- 모든 곳에서 함수의 결괏값이 동일하기 때문에 식의 순서를 변경하더라도 각 식의 결과는 달라지지 않는다.
...
하지만 명령-쿼리 분리 원칙을 사용하면 이 균열을 조금이나마 줄일 수 있다. 명령 - 쿼리 분리 원칙은 부수 효과를 가지는 명령으로부터 부수효과를 가지지 않는 쿼리를 명백하게 분리함으로써 제한적이나마 참조 투명성의 혜택을 누릴 수 있게 된다.
...
함수형 프로그래밍은 부수 효과가 존재하지 않는 수학적인 함수에 기반한다. 따라서 함수형 프로그래밍에서는 참조 투명성의 장점을 극대화 할 수 있으며 명령형 프로그래밍에 비해 프로그래밍의 실행 결과를 이해하고 예측하기 더 쉽다.

CQS 패턴이 가지는 장점과 왜 나오게 되었는지에 대해 이야기 하고 있다 생각합니다. 또한 CQS패턴의 장점이라고도 할 수 있는 참조 투명성이란 무엇인지 어떻게 CQS패턴은 참조 투명성이란 장점을 얻게 되었는지를 설명하고 있습니다.

' > 오브젝트 (완)' 카테고리의 다른 글

08 의존성 관리하기  (1) 2024.01.30
07 객체 분해  (1) 2024.01.24
5장 책임 할당하기  (0) 2024.01.17
4장 설계 품질과 트레이드오프  (0) 2024.01.16
3장 역할, 책임, 협력  (1) 2024.01.15