본문 바로가기

Study/오프젝트

06) 메시지와 인터페이스

 

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

 

1. 협력과 메시지

1.1 클라이언트-서버 모델

협력은 어떤 객체가 다른 객체에게 무언가를 요청할 때 시작된다. 메시지는 객체 사이의 협력을 가능하게 하는 유일한 매개체다. 두 객체 사이의 협력 관계를 설명하기 위해 사용하는 전통적인 메타포는 클라이언트-서버 모델이다.

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

      가격을 계산하라(메시지)
    --->
Screening(클라이언트)  -------------------------->  Movie(서버)
   <---
   예매 요금

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

외부의 객체는 오직 객체가 공개하는 메시지를 통해서만 객체와 상호작용할 수 있다. 이처럼 객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스라고 부른다. 퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션이라고 부른다. 오퍼레이션은 어떤 행동에 대한 추상화다.

 

앞선 영화예매에 대해서 생각을 해보면 할인의 조건인(DiscountCondition) 인터페이스에 정의된 isSatisfiedBy가 오퍼레이션에 해당된다. 메시지를 수신했을 때 실제로 실행되는 코드를 메서드라고 부른다. 실제 할인조건(기간할인, 인원할인) 등에서 정의된 isSatisfiedBy실제 구현을 포함하기 때문에 메서드라고 부른다. 기간할인, 인원할인의 isSatisfiedBy 메서드는 할인조건 인터페이스에 정의된 isSatisfiedBy 오퍼레이션의 구현 중 하나다.

 

 

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

좋은 인터페이스는 최소한의 인터페이스추상적인 인터페이스라는 조건을 만족해야 한다. 최소한의 인터페이스는 꼭 필요한 오퍼레이션만을 인터페이스에 포함한다. 추상적인 인터페이스는 어떻게 수행하는지가 아니라 무엇을 하는지를 표현한다.

 

퍼블릭 인터페이스의 품질에 영향을 미치는 원칙과 기법

  • 디미터 법칙
    • 객체 간의 협력을 설계할 때 캡슐화를 위반하는 메시지가 인터페이스에 포함되지 않도록 제한한다.
  • 묻지 말고 시켜라
    • 디미터 법칙을 준수하는 협력을 만들기 위한 스타일을 제시한다.
  • 의도를 드러내는 인터페이스
    • 객체의 퍼블릭 인터페이스에 어떤 이름이 드러나야 하는지에 대한 지침을 제공함으로써 코드의 목적을 명확하게 보여준다.
  • 명령-쿼리  분리

2.1 디미터 법칙

  • 객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한하라
  • 낯선 자에게 말하지 말라
  • 오직 인접한 이웃하고만 말하라
  • 오직 하나의 도트만 사용하라

무슨 말인지는 이해가 가지만 예시를 들어서 완벽히 이해를 해보자. 만약 한국차량들 중에서 벤츠라는 차량에 문제가 생겨서 전부 리콜을 해야 한다고 가정해 보자.

@Getter
public class People {
    private String name;
    private String region;
    private Car car;
}

@Getter
public class Car {
    private String brand;
    private String years;
}
public class EmailService {

    public void sendEmail(People people) {
        if (people.getCar().getBrand().equals("벤츠")) {
            recallMsg(people);
        }
    }
}

 

여기서 문제가 발생했다. 이전에 우리는 객체 지향 프로그래밍에서 가장 중요한 것은 객체가 어떤 데이터를 가지고 있는지가 아니라 어떤 메시지를 주고받는지이다. 지금 EmailService에서 객체에게 메시지를 보내는 것이 아니라 직접 가지고 있는 데이터를 확인하고 있다. 그렇기에 디미터의 법칙을 위배하며 객체 지향스럽지 않다. 위의 문제를 해결하기 위해서는 데이터를 확인하는 것이 아닌 메시지를 주고받도록 코드를 수정하는 것이다.

 

@Getter
public class People {
    private String name;
    private String region;
    private Car car;

    public boolean isCarBrandBenz() {
        return car.isBrandBenz();
    }
}

@Getter
public class Car {
    private String brand;
    private String years;

    public boolean isBrandBenz() {
        return brand.equals("벤츠");
    }
}
public class EmailService {

    public void sendEmail(People people) {
        if (people.isCarBrandBenz()) {
            recallMsg(people);
        }
    }
}

 

디미터 법칙을 따르는 코드는 메시지 수신자의 내부 구조가 전송자에게 노출되지 않으며, 메시지 전송자는 수진자의 내부 구현에 결합되지 않는다. 따라서 클라이언트와 서버 사이에 낮은 결합도를 유지할 수 있다. 디미터 법칙은 캡슐화를 다른 관점에서 표현한 것이다. 근본적으로 디미터 법칙을 위반하는 설계는 인터페이스와 구현의 분리 원칙을 위반한다.

 

2.2 묻지 말고 시켜라

디미터 법칙은 메시지는 객체의 상태에 관해 묻지 말고 원하는 것을 시켜야 한다는 사실을 강조한다. 묻지 말고 시켜라는 이런 스타일의 메시지 작성을 장려하는 원칙을 가리키는 용어이다. 묻지 말고 시켜라 원칙을 따르면 객체의 정보를 이용하는 행동을 객체의 외부가 아닌 내부에 위치시키기 때문에 자연스럽게 정보와 행동을 동일한 클래스 안에 두게 된다.

 

영화관에서 일한다고 생각해 보자. 우리는 카운터에서 영화예매를 도와주는 직원이다.
손님에게 무슨 영화를 볼 건지 물어보고 계산을 도와준다.
그런데 우리는 손님의 돈이 얼마 있는지에 대한 정보를 알 필요가 없고 계산금액을 받으면 된다.
그렇기에 손님에게 XXXX원이라는 금액을 알려주면 원하는 금액을 받으면 된다.

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

켄트 벡은 메서드를 명명하는 두 가지 방법을 설명했다.

첫째는 메서드가 작업을 어떻게 수행하는지를 나타내도록 이름을 짓는 것이다. 어떻게 수행하는지를 드러내는 이름이란 메서드의 내부 구현을 설명하는 이름이다. 객체가 협력 안에서 수행해야 하는 책임에 관해 고민해봐야 한다.

 

두 번째 방법은 '어떻게'가 아니라 '무엇'을 하는지를 드러내는 것이라고 한다. 메서드가 무엇을 하느냐에 초점을 맞추면 클라이언트의 관점에서 동일한 작업을 수행하는 메서드들을 하나의 타입 계층으로 묶을 수 있는 가능성이 커진다. 다양한 타입의 객체가 참여할 수 있는 유연한 협력을 얻게 되는 것이다.

객체에게 묻지 말고 시키되 구현 방법이 아닌 클라이언트의 의도를 드러내야 한다.

 

2.4 명령-쿼리 분리 원칙

  • 루틴 : 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈
    • 프로시저 : 정해진 절차에 따라 내부의 상태를 변경하는 루틴의 한 종류
    • 함수 : 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 한 종류
  • 명령 : 객체의 상태를 수정하는 오퍼레이션
  • 쿼리 : 객체와 관련된 정보를 반환하는 오퍼레이션

퍼블릭 인터페이스에 오퍼레이션을 정의할 때 참고할 수 있는 지침을 제공한다. 명령쿼리를 뒤섞으면 실행 결과를 예측하기가 어려워질 수 있다. 명령쿼리를 명확하게 분리함으로써 명령형 언어의 틀 안에서 참조 투명성의 장점을 제한적이나마 누릴 수 있게 된다. 그렇게 되면 버그가 적고, 디버깅이 용이하며, 쿼리의 순서에 따라 실행 결과가 변하지 않는 코드를 작성할 수 있게 된다.

'Study > 오프젝트' 카테고리의 다른 글

08) 의존성 관리하기  (0) 2024.01.02
07) 객체 분해  (0) 2023.12.27
05) 책임 할당하기  (0) 2023.12.11
04) 설계 품질과 트레이드오프  (1) 2023.12.07
03) 역할, 책임, 협력  (2) 2023.12.03