본문으로 바로가기

06_메시지와 인터페이스

category 기술서적/오브젝트 2025. 4. 17. 15:47
728x90
반응형
SMALL

06_메시지와 인터페이스

퍼블릭 인터페이스의 품질에 영향을 주는것

퍼블릭 인터페이스란 객체가 외부랑 소통하기 위해 만든 메서드가 퍼블릭 인터페이스

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

디미터 법칙

  • 다른 객체 내부 구조에 강하게 결합되자 않도록 해라

  • 오로지 하나의 . 만 사용해라


클래스 내부의 메서드는 아래의 조건을 만족하는 인스턴스에만 메시지를 전송하도록 프로그래밍해야 한다.

  • this 객체
  • 메서드의 매개변수
  • this의 속성
  • this의 속성은 컬렉션의 요소
  • 메서드 내에서 생성된 객체

worst

public class ReservationAgency {

    public Reservation reserve(Screening screening, Customer customer, int audienceCount){
         Movie movie = screening.getMovie();

         boolean discountable = false;
        for(DiscountCondition condition : movie.getDiscountConditions()){
            if(condition.getType() == DiscountConditionType.PERIOD){
                    condition.getStartTime().compareTo(screening.getWhenScreend.toLocalTime())
                    .....
            }
        }
        ...
    }
}

best

public class ReservationAgency {

    public Reservation reserve(Screening screening, Customer customer, int audienceCount){
        Movie fee = screening.calculateFee(audienceCount);
        return new Reservation(customer, screening, fee audienceCount);
    }
}

best에서는 reserve메서드의 인자로 전달된 Screening 인스턴스에게만 메시지를 전송하며, Screening 내부에 대한 어떤 정보도 알지 못함


디미터 법칙을 따르는 코드는 메시지 수신자의 내부 구조가 전송자에게 노출되지 않으며, 메시지 전송자는 수신자의 내부 구현에 결합되지 않는다. 따라서 클라이언트와 서버 사이의 낮은 결합도를 유지할 수 있다.


디미터 법칙이 가치 있는 이유는 클래스를 캡슐화하기 위해 따라야하는 구체적인 지침을 제공하기 때문이다.

  • 디미터 법칙은 협력하는 클래스의 캡슐화를 지키기 위해 접근해야 하는 요소를 제한한다.
  • 클래스의 내부 구현을 채워가는 동시에 현재 협력하고 있는 클래스에 관해서도 고민하도록 주의를 환기시킨다.



디미터 법칙을 위반하는 코드의 전형적인 모습

screening.getMovie().getDiscountConditions();

위와 같은 코드를 기차 충돌이라고 부른다.

내부의 구현이 외부로 노출됐을때 나타나는 전형적인 형태로, 메시지 전송자는 메시지 수신자의 내부 정보를 자세히 알게된다.

따라서 메시지 수신자의 캡슐화는 무너지고, 전송자는 수신자의 내부 구현에 강하게 결합된다.

그래서 아래와 같이 원하는것이 무엇? 인지를 명시하고, 단순히 수행해야 한다.

screening.calculateFee(audienceCount);


묻지 말고 시켜라

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

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

내부의 상태를 묻는 오퍼레이션을 인터페이스에 포함시키고 있다면 더 낳은 방법은 없는지 고민하라. ex)요청하는 오퍼레이션으로 대체

내부의 상태를 이용해 어떤 결정을 내리는 로직이 객체 외부에 존재하면 그것은 해당 객체가 책임져야 하는 행동이 외부로 누수된 것이다.

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


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

public class PeriodCondition {
    public boolean isSatisFiedByPeriod(Screening){...}
}

public class SequenceCondition {
    public boolean isSatisFiedBySequence(Screening){...}
}

위와 같은 스타일은 좋지 않다.

  • 메서드 이름이 다르기 때문에 메서드의 구현을 정확히 알아야 사용할 수 있다.

  • 메서드 수준에서 캡슐화를 위반한다. 위 스타일은 클라이언트로 하여금 협력하는 객체의 종류를 알도록 강요한다.

  • 변경에 취약하다. 메서드의 이름 또는 로직이 변경된다면, 클라이언트의 로직도 함께 변경해야한다.


public interface DiscountCondition {
    boolean isSatisfiedBy(Screening screening);
}

어떻게 수행하느냐 보다는 무엇을 수행하느냐? 라는것에 초점을 맞춰 인터페이스를 만들었다.

어떻게?(ByPeriod, BySequence) 보다는 무엇을?(isSatisfied)


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

IntStream.of(1,2,3,4,5).filter(x -> x>2).distinct().count();

자기 자신의 인스턴스, 동일한 클래스의 인스턴스를 반환하고 있기때문에 디미터 법칙을 위반하지 않는 코드이다.

디미터 법칙은 결합도와 관련된 것이며, 이 결합도가 문제되는 것은 객체의 내부구조가 외부로 노출되는 경우로 한정된다.

로버트 마틴은 클린코드에서 디미터 법칙의 위반 여부는 묻는 대상이 객체인지, 자료구조인지에 달려있다고 말한다.

객체는 디미터 법칙을 따르는것이 좋지만, 자료구조라면 당연히 내부를 노출해야한다.


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

소프트웨어 설계에 법칙이란 존재하지 않는다. 원칙을 맹신하지 마라. 모든것은 Trade-off이며 설계는 그것의 산물이다.



명령-쿼리 분리 원칙

가끔은 필요에 따라서 물어봐야 한다는 사실에 납득이 되면, 명령-쿼리 분리 원칙은 도움이 된다.

객체의 상태를 변경하는것을 명령, 객체와 관련된 정보를 반환하는것을 쿼리라고 부른다.

명령과 쿼리를 분리하기 위해서는 2가지 규칙을 준수해야 한다.

  • 객체의 상태를 변경하는 명령은 반환값을 가질 수 없다.
  • 객체의 정보를 반환하는 쿼리는 상태를 변경할 수 없다.

질문이 답변을 수정해서는 안 된다.


아래와 같이 반환값을 갖는것은 쿼리 없는것은 명령으로 반환 값 여부로 쉽게 구분할 수도 있다.

public class Event {
    public boolean isSatisFied(Schedule schedule){...} //명령
    public void reSchedule(Schedule schedule){...} //쿼리
}

명령과 쿼리를 분리하면 코드에서 일어나는 부수효과를 줄일 수 있다.



책임에 초점을 맞춰라

메시지를 먼저 선택하고 그 후에 메시지가 처리할 객체를 선택하면, 의도를 드러내는 인터페이스를 쉽게 설계 할 수 있다.

728x90
반응형
LIST

'기술서적 > 오브젝트' 카테고리의 다른 글

10. 상속과 코드 재사용  (1) 2025.04.18