6장: 메시지와 인터페이스

협력과 메시지

  • 협력 안에서 메시지를 전송하는 객체를 클라이언트, 메시지를 수신하는 객체를 서버라고 한다.

  • 협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다.

  • 객체가 수신하는 메시지의 집합에만 초점을 맞추지만 협력에 적합한 객체를 설계하기 위해서는 외부에 전송하는 메시지의 집합도 함께 고려하는 것이 바람직하다.

  • 메시지는 다른 객체에게 도움을 요청하기 위한 의사소통 수단이며, 오퍼레이션 이름과 인자로 구성된다.

    • 언어별로 메시지 전송 표현법이 다르다.

      • Java : receiver.operationName(argument)

      • Ruby: receiver operationName argument

  • 메서드는 메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저이다.

    • 런타임에 수신자의 클래스에 기반해서 메시지를 메서드에 바인딩한다.

  • 퍼블릭 인터페이스란 객체가 의사소통을 위해 외부에 공개하는 메시지의 집합이다.

  • 오퍼레이션이란 퍼블릭 인터페이스에 포함된 메시지이며 수행 가능한 어떤 행동에 대한 추상화이다. 동일한 오퍼레이션을 호출하더라도 실제로는 서로 다른 메서드들이 실행될 수 있다.

  • 시그니처란 오퍼레이션 이름과 파라미터 목록을 합친 것이다.

인터페이스와 설계 품질

디미터 법칙

  • 클래스를 캡슐화하기 위해 따라야하는 구체적인 지침을 나타낸다.

  • 객체들의 협력 경로를 제한하면 결합도를 효과적으로 낮출 수 있다.

  • 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 프로그래밍해야 한다.

  • 한 클래스에서는 this 객체, 메서드의 매개변수로 입력된 객체, this의 속성, this의 속성인 컬렉션의 요소, 메서드 내에서 생성된 지역 객체에만 메시지를 전송하도록 해야 한다.

  • “오직 하나의 도트만 사용하라”는 말로 요약할 수도 있다.

  • 아래와 같이 계속해서 객체를 타고타고가 메서드를 호출하는 방법은 결합도가 높아지는 안좋은 예이다. 이 경우 기차의 칸이 이어진 것 처럼 보이기 때문에 train wreck이라고 부른다.

  • 객체의 내부 구조를 묻는 메시지가 아니라 수신자에게 무언가를 시키는 메시지가 더 좋은 메시지이다.

Tell, Don’t Ask

  • 객체의 상태에 대해 묻는 대신 원하는 것을 말하는 형태로 설계해야 한다.

  • 객체 간의 상호작용을 좀 더 명시적으로 만들고 이름을 갖도록 강요한다.

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

  • 메서드명이 작업을 어떻게 수행하는지에 대한 방법을 나타낸다면, 제대로 커뮤니케이션이 이뤄지지 않을 수 있다. 또한 메서드 수준에서의 캡슐화를 위반하여 클라이언트가 협력하는 객체를 알도록 강요한다.

  • 메서드명에는 무엇을 하는지에 대한 내용을 포함해야 이해하기 쉽고 유연한 코드를 만들 수 있다. 동일한 작업을 수행하는 메서드들을 하나의 타입으로 묶을 수 있는 가능성이 커진다.

  • 구현과 관련된 모든 정보를 캡슐화하고 객체의 퍼블릭 인터페이스에는 협력과 관련된 의도만을 표현해야 한다.

원칙의 함정

디미터 법칙과 도트(.)

  • 디미터 법칙은 단순히 하나의 도트를 강제하는 규칙이 아니다. 자바에서 제공하는 스트림 메서드 체이닝의 경우 여러 개의 도트가 사용되지만 모두 동일한 클래스의 인스턴스를 반환하는 메서드들로 구성되기 때문에 디미터 법칙을 위반하지 않는다.

  • 중요한 것은 객체 내부 구조를 노출하고 있는가에 대한 것이다.

결합도와 응집도의 충돌

  • 모든 상황에서 디미터 법칙과 묻지 말고 시켜라 원칙을 따르기 위해 맹목적으로 위임 메서드를 추가하면 같은 퍼블릭 인터페이스 안에 어울리지 않는 오퍼레이션들이 공존하게 되어 애플리케이션은 응집도가 낮은 객체로 넘쳐날 것이다.

  • 아래와 같이 상영 시간에 대한 정보를 가져와 기간 할인 조건에 해당하는지 확인하는 메서드는 Screening 의 내부 상태를 가져와 확인하기 때문에 캡슐화를 위반하는 것으로 보일 수 있다.

  • 이 때 Screening에 묻지 말고 시켜라 방식을 적용하기 위해 isDiscountable 메서드를 추가한다면 Screening의 본질적인 책임인 영화 예매와 동떨어진 역할을 떠맡게 되어 객체의 응집도가 낮아진다.

  • Screening의 캡슐화를 향상시키는 것보다 Screening의 응집도를 높이고 Screening과 PeriodCondition 사이의 결합도를 낮추는 것이 전체적인 관점에서 더 좋은 방법이다.

명령 쿼리 분리 원칙

  • 루틴이란 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈이다.

    • 프로시저는 정해진 절차에 따라 내부의 상태를 변경하는 루틴의 한 종류이다. 부수효과를 발생시킬 수 있으나 값을 반환할 수 없다.

    • 함수는 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 한 종류이다. 값을 반환할 수 있으나 부수효과를 발생시킬 수 없다.

  • 객체의 상태를 수정하는 오퍼레이션을 명령이라고 부르고 객체와 관련된 정보를 반환하는 오퍼레이션을 쿼리라고 부른다.

  • 개념적으로 명령은 프로시저와 동일하고 쿼리는 함수와 동일하다.

  • 오퍼레이션은 부수효과를 발생시키는 명령이거나 부수효과를 발생시키지 않는 쿼리 중 하나여야 한다.

  • 어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안된다. 명령과 쿼리를 뒤섞으면 실행 결과를 예측하기가 어려워질 수 있다.

참조 투명성

  • referential transparency, 어떤 표현식 e가 있을 때 e의 값으로 e가 나타나는 모든 위치를 교체하더라도 결과가 달라지지 않는 특성을 의미한다.

  • 또한 식의 순서를 변경하더라도 결과가 달라지지 않는다.

  • 다음과 같이 두 가지 장점이 있다.

    • 모든 함수를 이미 알고 있는 하나의 결괏값으로 대체할 수 있기 때문에 식을 쉽게 계산할 수 있다.

    • 모든 곳에서 함수의 결괏값이 동일하기 때문에 식의 순서를 변경하더라도 각 식의 결과는 달라지지 않는다.

  • 명령-쿼리 분리 원칙은 부수효과를 가지는 명령으로부터 부수효과를 가지지 않는 쿼리를 명백하게 분리하여 제한적이나마 참조 투명성의 혜택을 누릴 수 있게 된다.

함수형 프로그래밍은 부수 효과가 존재하지 않는 수학적인 함수에 기반하여 참조 투명성의 장점을 극대화할 수 있으며 명령형

Last updated