Skip to content

Latest commit

 

History

History
220 lines (151 loc) · 12.9 KB

20240326.md

File metadata and controls

220 lines (151 loc) · 12.9 KB

오브젝트 책 읽기 스터디

6. 메시지와 인터페이스

0. 서론

  • 클래스는 구현 도구에 불과하다.

  • 애플리케이션은 클래스로 구성되지만 메시지를 통해 정의된다.

  • 객체가 수신하는 메시지들이 객체의 퍼블릭 인터페이스를 구성한다.

  • 훌륭한 퍼블릭 인터페이스를 얻기 위해서는 책임 주도 설계 방법을 따르는 것만으로는 부족하다.

    • 유연하고 재사용 가능한 퍼블릭 인터페이스를 만드는 데 도움이 되는 설계 원칙과 기법을 익히고 적용해야 한다.

1. 협력과 메시지

클라이언트-서버 모델

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

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

  • 객체는 협력에 참여하는 동안 클라이언트와 서버의 역할을 동시에 수행하는 것이 일반적이다.

메시지와 메시지 전송

  • **메시지(message)**는 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단이다.

  • 한 객체가 다른 객체에게 도움을 요청하는 것: 메시지 전송(message sending) 또는 메시지 패싱(message passing) 이라고 부른다.

  • 메시지를 전송하는 객체를 메시지 전송자(message sender), 메시지를 수신하는 객체를 메시지 수신자(message receiver) 라고 부른다.

  • 메시지는 오퍼레이션명(operation name)인자(argument) 로 구성된다.

    • 메시지 전송은 여기에 메시지 수신자를 추가한 것이다.
  • 따라서 메시지 전송은 메시지 수신자, 오퍼레이션명, 인자의 조합이다.

메시지와 메서드

  • 메시지를 수신했을 때 실제로 어떤 코드가 실행되는지는 메시지 수신자의 실제 타입에 따라 달라진다.

    • 다형성을 이용한 코드가 그 예시
  • 메시지를 수신했을 때 실제로 수행되는 함수 또는 프로시저를 메서드 라고 부른다.

    • 동일한 이름의 변수에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다.
    • 객체는 메시지와 메서드라는 두 가지 서로 다른 개념을 실행 시점에 연결한다.
    • 즉 컴파일 시점과 실행 시점의 의미가 달라진다.
  • 메시지와 메서드의 구분은 메시지 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 한다.

    • 메시지 전송자는 수신자가 어떤 클래스의 인스턴스인지 어떤 방식으로 요청을 처리하는지 모른다.
    • 메시지 수신자는 누가 메시지를 전송하는지 모른다.
  • 실행 시점에 메시지와 메서드를 바인딩함으로써 두 객체 사이의 결합도를 낮춘다.

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

  • 외부에서는 객체가 공개하는 메시지를 통해서만 객체와 상호작용을 할 수 있다.

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

  • 퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션(operation) 이라고 부른다.

    • 메서드는 오퍼레이션의 여러 가능한 구현 중 하나이다.
  • 오퍼레이션은 구현이 아닌 추상화이다.

  • 메서드는 오퍼레이션을 구현한 것이다.

  1. 메시지 전송
  2. 오퍼레이션 호출
  3. 메서드 실행

시그니처

  • 오퍼레이션(또는 메서드)의 이름과 파라미터 목록을 합쳐 시그니처(signature) 라고 부른다.
    • 오퍼레이션은 실행 코드 없이 시그니처만을 정의한 것이다.
    • 메서드는 시그니처에 구현을 더한 것이다.
  • 메시지가 수신되면 오퍼레이션의 시그니처와 동일한 메서드가 실행된다.

2. 인터페이스와 설계 품질

  • 좋은 인터페이스는 최소한의 인터페이스와 추상적인 인터페이스라는 조건을 만족해야 한다.

    • 최소한의 인터페이스는 꼭 필요한 오퍼레이션만을 인터페이스에 포함한다.
    • 추상적인 인터페이스는 어떻게 수행하는지가 아니라 무엇을 하는지를 표현한다.
  • 최소주의를 따르면서 추상적인 인터페이스를 설계할 수 잇는 가장 좋은 방법은 책임 주도 설계 방법을 따르는 것이다.

  • 이와는 별개로 훌륭한 인터페이스가 가지는 공통적인 특징을 알면 좋다.

    • 디미터 법칙
    • 묻지 말고 시켜라
    • 의도를 드러내는 인터페이스
    • 명령-쿼리 분리

디미터 법칙

  • 객체의 내부 구조에 대한 결합으로 인해 발생하는 설계 문제를 해결하기 위해 제안된 원칙이다.

  • 디미터 법칙(Law of Demeter): 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하라.

    • 낯선 자에게 말하지 말라
    • 오직 인접한 이웃하고만 말하라
    • 오직 하나의 도트(.)만 사용하라
  • 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 하자.

    • this 객체
    • 메서드의 매개변수
    • this의 속성
    • this의 속성인 컬렉션의 요소
    • 메서드 내에서 생성된 지역 객체
screening.getMovie().getDiscountConditions();
  • 위와 같은 코드를 기차 충돌(train wreck) 이라고 부른다.

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

  • 디미터 법칙은 훌륭한 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야 한다는 사실을 강조한다.

묻지 말고 시켜라

  • 묻지 말고 시켜라(Tell, Don't Ask)

  • 메시지 전송자는 메시지 수신자의 상태를 기반으로 결정을 내린 후 수신자의 상태를 바꿔서는 안 된다.

  • 절차적인 코드는 정보를 얻은 후에 결정한다. 객체지향 코드는 객체에게 그것을 하도록 시킨다.

  • 내부의 상태를 묻는 오퍼레이션을 인터페이스에 포함하고 있다면 더 나은 방법은 없는지 고민해보자.

  • 상태를 묻는 오퍼레이션을 행동을 요청하는 오퍼레이션으로 대체하자.

  • 단순히 객체에게 묻지 않고 시킨다고 해서 모든 문제가 해결되는 것은 아니다.

  • 인터페이스는 객체가 어떻게 하는지가 아니라 무엇을 하는지를 서술해야 한다.

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

  • 메서드를 명명하는 두 가지 방법

    • 메서드가 작업을 어떻게 수행하는지를 나타내도록 이름 짓기
    • '어떻게'가 아니라 '무엇'을 하는지 드러내기
  • 어떻게 수행하는지를 드러내는 이름은 메서드의 내부 구현을 설명하는 이름이다.

  • 무엇을 하는지 드러내도록 메서드의 이름을 짓기 위해서는 객체가 협력 안에서 수행해야 하는 책임에 관해 고민해야 한다.

  • 무엇을 하느냐에 초점을 맞추면 클라이언트의 관점에서 동일한 작업을 수행하는 메서드들을 하나의 타입 계층으로 묶을 수 있다.

  • 의도를 드러내는 선택자(Intention Revealing Selector)

    • 매우 다른 두 번째 구현을 상상하고 해당 메서드에 동일한 이름을 붙인다고 생각하기

함께 모으기

  • 디미터 법칙을 위반하는 설계은 인터페이스 구현의 분리 원칙 을 위반한다.
    • 클라이언트에게 객체의 구현을 노출하고 있음
  • 디미터 법칙과 묻지 말고 시켜라 스타일을 따르는 인터페이스를 얻었다면 인터페이스가 클라이언트의 의도를 올바르게 반영했는지 확인해야 한다.
  • 오퍼레이션의 이름은 클라이언트의 의도를 표현하는 이름을 가져야 한다.

3. 원칙의 함정

  • 소프트웨어 설계에 법칙이란 존재하지 않은다.
    • 법칙에는 예외가 없지만 원칙에는 예외가 넘쳐난다.
  • 초보자는 원칙을 맹목적으로 추종한다.
    • 적용하려는 원칙들이 서로 충돌하는 경우에도 원칙에 정당성을 부여하고 억지로 끼워 맞추려고 노력한다.
  • 원칙이 현재 상황에 부적합하다고 판단된다면 과감하게 원칙을 무시하라.

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

  • 하나 이상의 도트를 사용하는 모든 케이스가 디미터 법칙 위반은 아니다.
  • 객체 내부 구현에 대한 어떤 정보도 외부로 노출하지 않는다면 그것은 디미터 법칙을 준수한 것이다.

결합도와 응집도의 충돌

  • 클래스는 하나의 변경 원인만을 가져야 한다.

    • 서로 상관없는 책임들이 함께 뭉쳐있는 클래스는 응집도가 낮으면 작은 변경으로도 쉽게 무너질 수 있다.
  • 묻지 말고 시켜라 스타일을 준수하는 퍼블릭 인터페이스만을 목표로 하다간 적절하지 못한 곳에 책임이 할당될 수 있다.

  • 자료 구조라면 내부를 노출해야 하므로 디미터 법칙을 적용할 필요가 없다.

  • 객체에게 시키는 것이 항상 가능하지 않기에 가끔씩은 물어야 한다.

4. 명령-쿼리 분리 원칙

  • 명령-쿼리 분리(Command-Query Separation) 원칙: 퍼블릭 인터페이스에 오퍼레이션을 정의할 때 참고할 수 있는 지침을 제공한다.

  • 루틴(routine): 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈

  • 루틴은 프로시저(procedure)함수(function) 으로 구분할 수 있음

    • 프로시저는 정해진 절차에 따라 내부의 상태를 변경하는 루틴의 한 종류
    • 함수는 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 한 종류
  • 명령(Command)쿼리(Query) 는 객체의 인터페이스 측면에서 프로시저와 함수를 부르는 또 다른 이름이다.

    • 객체의 상태를 수정하는 오퍼레이션을 명령이라고 부른다.
    • 객체와 관련된 정보를 반환하는 오퍼레이션을 쿼리라고 부른다.
  • 명령은 프로시저와 동일하고 쿼리는 함수와 동일하다.

  • 어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안된다.

    • 객체의 상태를 변경하려는 명령은 반환값을 가질 수 없다.
    • 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.
  • 질문이 답변을 수정해서는 안 된다.

    • 명령은 상태를 변경할 수 있지만 상태를 반환해서는 안 된다.
    • 쿼리는 객체의 상태를 반환할 수 있지만 상태를 변경해서는 안된다.

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

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

  • 겉으로 보기에는 쿼리처럼 보이지만 내부적으로 부수효과를 가지는 메스드는 이해하기 어렵고 잘못 사용하기 쉽다.

  • 명령과 쿼리를 분리하면 인터페이스가 복잡해진 것처럼 보이지만 그로써 얻는 이점이 더 크다.

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

  • 명령과 쿼리를 분리하면 명령형 언어의 틀 안에서 참조 투명성(referential transparency) 의 장점을 제한적이나마 누릴 수 있다.

    • 어떤 표현식 e가 있을 때 e의 값으로 e가 나타나는 모든 위치를 교체하더라도 결과가 달라지지 않는 특성
    • 표현식 f(1)을 그 값인 3으로 바꿔도 결과가 달라지지 않는 특성
  • 수학은 참조 투명성을 엄격하게 준수하는 가장 유명한 체계다.

  • 부수효과(side effect)

    • 컴퓨터는 x라는 값을 초기화한 후 다른 값으로 변경하는 것이 가능하다.
  • 참조 투명성을 만족하는 식은 두 가지 장점을 제공한다.

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

함수형 프로그래밍(functional programming)

  • 부수효과가 존재하지 않는 수학적인 함수에 기반한다.
  • 참조 투명성의 장점을 극대화 할 수 있다.
  • 명령형 프로그래밍에 비해 프로그램의 실행 결과를 이해하고 예측하기가 더 쉽다.
  • 병렬 처리에서 강점이 있다.

책임에 초점을 맞춰라

  • 지금까지 배운 원칙을 잘 적용하기 위해서는 메시지를 먼저 선택하고 그 후에 메시지를 처리할 객체를 선택하면 된다.

  • 오퍼레이션의 시그니처는 단지 오퍼레이션의 이름과 인자의 반환값의 타입만 명시할 수 있다.

  • 시그니처에는 어떤 조건이 만족돼야만 오퍼레이션을 호출할 수 있고 어떤 경우에 결과를 반환받을 수 없는지를 표현할 수 없다.

  • 이를 위해서는 계약에 의한 설계 라는 개념이 있다.