본문 바로가기
독서 기록

[오브젝트] 6장 - 메시지와 인터페이스

by 워냥 2024. 3. 28.

내용 정리: 20240326 TIL

1. 개요

6장에서는 인터페이스 구성에 집중한다.

좋은 인터페이스를 얻기 위해 적용할 수 있는 다양한 원칙들을 알아보자.


2. 메시지

메시지의 구성

메시지 전송은 한 객체가 다른 객체에게 도움을 요청하는 것이다.

여기서 요청하는 객체를 클라이언트, 요청을 받는 객체를 서버라고 한다.

 

메시지 전송은 메시지 수신자(message receiver), 오퍼레이션명(operation name), 인자(argument)로 구성된다.

 

bag.hold(ticket);

 

위 코드에서 hold는 오퍼레이션명, ticket은 인자, bag은 메시지 수신자이다.

bag이라는 메시지 수신자에게 ticket이라는 인자를 주며 hold라는 오퍼레이션을 시키는 것이다.

 

오퍼레이션명과 인자를 합쳐 시그니처(signature)라고 부른다.

 

메시지와 메서드

다형성에서 봤듯이 메시지를 수신했을 때 실제로 실행되는 코드는 메시지 수신자의 타입에 따라 달라진다.

여기서 실제로 실행되는 코드를 메서드라고 부른다.

메서드는 오퍼레이션의 여러 구현 중 하나로 볼 수 있다.

 

메시지 호출 과정

메시지 호출은 다음 과정으로 진행된다.

 

  1. 클라이언트 객체가 메시지를 전송한다.
  2. 메시지에 해당하는 오퍼레이션이 호출된다.
  3. 메시지를 받은 서버 객체의 타입에 맞는 메서드가 실행된다.

3. 좋은 인터페이스를 위한 가이드

좋은 인터페이스는 다음 두 가지 조건을 만족한다.

 

  1. 꼭 필요한 오퍼레이션만을 포함한다.
  2. 어떻게 수행하는지가 아닌 무엇을 하는지를 표현한다.

위 조건을 만족시키기 위한 여러 가지 가이드를 살펴보자.

 

디미터 법칙

디미터 법칙(Law of Demeter)는 객체끼리의 강한 결합을 방지하기 위한 가이드를 제시한다.

 

내부 구현이 외부로 노출되었을 때의 형태를 살펴보자.

 

screening.getMovie().getDiscountConditions();

 

getter가 계속해서 호출되는 코드를 기차 충돌(train wreck)라고 부른다.

기차 충돌은 클래스의 내부 구현이 외부로 노출되었을 때 나타나는 대표적인 형태다.

 

구현이 외부로 노출되는 것을 막기 위해 클래스가 메시지를 전송하는 대상을 다음으로 제한하자.

 

  1. this 객체
  2. 메서드의 매개변수
  3. this의 속성
  4. this의 속성인 컬렉션의 요소
  5. 메서드 내에서 생성된 지역 객체

디미터 법칙은 인접한 객체끼리만 소통하게 함으로써 내부 구현이 외부로 노출되는 것을 방지한다.

 

묻지 말고 시켜라

묻지 말고 시켜라(Tell, Don't Ask)는 메시지 전송자가 수신자의 상태를 얻은 뒤 수신자의 상태를 바꾸는 행동을 막는다.

 

if (audience.getBag().hasInvitation()) {
    Ticket ticket = ticketSeller.getTicketOffice().getTicket();
    audience.getBag().setTicket(ticket);
}

 

위 코드에서는 Bag의 상태를 확인한 뒤 Bag에 Ticket을 전달하고 있다.

이는 정보를 얻은 후에 결정하는 절차지향적인 방식이다.

 

객체지향적 코드는 객체에게 묻지 않고 스스로 판단하도록 한다.

내부의 상태를 묻는 오퍼레이션을 행동을 요청하는 오퍼레이션으로 교체하자.

 

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

메서드의 이름을 지을 때는 해당 메서드가 무엇을 하는지 드러내자.

메서드가 어떻게 수행하는지 드러내는 이름은 내부 구현을 설명하게 된다.

 

무엇을 하는지에 초점을 맞추면 동일한 작업을 수행하는 메서드들을 하나의 타입으로 묶을 수 있다.

메서드의 이름은 클라이언트의 의도를 표현할 수 있도록 짓자.


4. 원칙의 함정

이 부분에서 다음 문장이 인상 깊었다.

소프트웨어 설계에 법칙이란 존재하지 않는다. 법칙에는 예외가 없지만 원칙에는 예외가 넘쳐난다.
초보자는 원칙을 맹목적으로 추종한다.
적용하려는 원칙들이 서로 충돌하는 경우에도 원칙에 정당성을 부여하고 억지로 끼워 맞추려고 노력한다.

 

지금까지 제시된 원칙들은 하나의 가이드일 뿐이다.

좋은 설계로 가기 위한 길을 제시한 것이지 정답을 말한 것이 아니다.

 

설계는 트레이드오프의 연속이다.

원칙을 맹목적으로 추종하지 말고 내 선택이 어떤 트레이드오프를 가져오는지에 집중하자.


5. 명령-쿼리 분리 원칙

명령-쿼리 분리(Command-Query Separation) 원칙은 인터페이스에 오퍼레이션을 정의할 때 참고할 수 있는 가이드를 제공한다.

명령과 쿼리를 구분하면 오퍼레이션을 명확히 할 수 있다.

 

명령과 쿼리

명령(Command)은 객체의 상태를 수정하는 오퍼레이션이다.

정해진 절차에 따라 내부의 상태를 변경하는 작업을 명령이라고 한다.

 

쿼리(Query)는 객체와 관련된 정보를 반환하는 오퍼레이션이다.

어떤 절차에 따라 필요한 값을 계산해서 반환하는 작업을 쿼리라고 한다.

 

명령과 쿼리의 분리

명령은 객체의 상태를 변경하지만 반환값을 가질 수 없다.

쿼리는 객체의 정보를 반환하지만 상태를 변경할 수 없다.

 

코드를 작성할 때 명령과 쿼리가 뒤섞이면 결과를 예측하기 어렵다.

쿼리처럼 보이지만 내부에서 정보를 변경하는 경우에는 버그가 발생하기 쉽다.

 

명령과 쿼리를 분리하면 오용으로 인한 오류를 줄일 수 있다.


6. 느낀 점

이번 장에서는 좋은 인터페이스를 설계하기 위한 구체적인 원칙에 대해 살펴보았다.

특히 예시에 나온 기차 충돌은 실제 코드를 작성할 때 경험해 본 적이 있어 더욱 와닿았던 것 같다.

 

명령-쿼리 분리도 인상 깊었다.

버그가 발생할 때의 대부분은 한 함수에서 발생한 예상하지 못한 부수 효과(side effect)가 원인이었다.

부수 효과를 통제하기 위해 명령과 쿼리를 신경 써서 구분해야겠다.

 

무엇보다 지금까지 살펴본 원칙들을 맹목적으로 추종해서는 안된다는 것을 알았다.

각 원칙의 목적이 무엇인지 제대로 이해한 뒤 사용해야 한다.

원칙을 준수한 코드가 아닌 좋은 코드를 작성해야 한다는 것을 명심해야겠다.

댓글