본문 바로가기

OBJECT

오브젝트 챕터6<메시지와 인터페이스>

이번 장에서는 훌륭한 퍼블릭 인터페이스를 만드는 데 도움이 되는 설계 원칙과 기법을 배웁니다.

전 장에서 배운 RDD가 여기에 속하지만 이로는 부족하기 때문입니다.

 


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

 

 

용어 정리

메시지 객체들이 협력을 위해 사용하는 의사소통 수단
메시지 전송, 메시지 패싱 하나의 객체가 다른 객체에게 도움을 요청하는 것
클라이언트, 메시지 전송자 메시지를 전송하는 객체
서버, 메시지 수신자 메시지를 수신하는 객체

 

 

메시지 구성 요소

오퍼레이션명 + 인자 + (메시지 수신자(메시지 전송시 추가))

 

메시지와 메서드의 구분??

메서드를 실행하는 객체의 타입을 고려하지 않아도 된다는 말인가?

 

 

 

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

책의 설명만으로는 둘의 개념 차이가 이해되지 않아서 추가적으로 정리해봤습니다.

구분 오퍼레이션(Operation) 메서드(Method)
본질 객체가 수행해야 할 추상적인 요청 오퍼레이션을 수행하기 위한 구체적ㅇ니 구현
위치 인터페이스 또는 추상 클래스 구체적인 클래스 내부
초점 무엇을 해야 하는가? 어떻게 해야 하는가?

 

 

자식 클래스에는 오퍼레이션과 메서드가 모두 있다.

자식 클래스의 메서드는 부모 클래스의 오퍼레이션을 구체화한 결과이다.

 

1. 클라이언트가 메시지를 전송한다.

2. 인터페이스나 추상 메서드가 이를 수신해서 오퍼레이션을 호출한다.

3. 이후 적절한 구현 클래스에 메시지를 전달해 메서드를 실행한다.

 

고민한 결과입니다. 맞는지 더 고민해봐야 하겠습니다...

 


 

디미터 법칙

객체의 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한. (간단히 오직 하나의 도트(.)만 이용해 메시지를 전달해라.)

 

단,

이를 맹목적으로 따르려 하지 말고 객체의 내부 구조를 노출하고 있는지 확인해야 한다.

 

 

 

Shy Code(캡슐화 기법)

불필요한 어떤 것도 다른 객체에게 보여주지 않으며, 다른 객체의 구현에 의존하지 않는다.

 

묻지 말고 시켜라

상태를 묻지 말고, 행동을 요청하라.

 

 


 

Interface의 의도를 드러내는 방법 

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

=> 무엇을 할지는 구현 클래스의 책임이기 때문이다. (유연성, 확장성, 다형성에 이점)

 

 

 

인사이트를 얻은 부분.

public class PeriodCondition{
    public boolean isSatisfiedByPeriod(Screening screening)
}

public class SequenceCondition{
    public boolean isSatisfiedBySequence(Screening screening)
}

 

위의 코드역시 캡슐화를 위반하고 있다는 것을 이해했다.

클라이언트로 하여금 협력하는 객체의 종류를 알도록 강요하고 있다는 것.

 

따라서 아래와 같은 스타일로 수정하는 것이 좋겠다.

public class PeriodCondition implements DiscountCondition{
    public boolean isSatisfiedBy(Screening screening)
}

public class SequenceCondition implements DiscountCondition{
    public boolean isSatisfiedBy(Screening screening)
}

// 클라이언트 시점에서 두 메서드를 동일한 방식으로 사용하도록
// 상위 인터페이스를 정의해 동일한 타입으로 설정한다.

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

 

 

 

인터페이스에 의도를 드러내자.

 

Theater의 setTicket 보다는 sellTo가

Bag의 setTicket 보다는 hold가

Audience의 setTicket보다는 buy가

클라이언트의 의도를 보다 분명하게 표현하는 방법이다.

 

 


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

개념을 정의하기에 앞서 용어들을 정리해야 하겠습니다.(너무 헷갈리기 때문에..)

다음 정의할 두 용어는 반환값의 유무에 따라 구분됩니다. 

        명령 = 프로시저

 쿼리 = 함수

 

객체의 상태를 수정하는 오퍼레이션을 명령(프로시저), 객체와 관련된 정보를 반환하는 오퍼레이션을 쿼리(함수)라고 합니다.

 

객체의 상태를 반환하는 getter는 쿼리, 객체의 상태를 수정하는 setter는 명령이라고 할 수 있겠습니다.

 

 

 

저자는 스케줄러를 활용해 부연했습니다.

public boolean isSatisfied (RecurringSchedule schedule){
    if (from.getDayOfWeek() != schedule.getDayOfWeek() || !from.toLocalTime().equals(schedule.getFrom()) ||              !duration.equals(schedule.getDuration())){
        reschedule(schedule);// 이벤트 상태 수정
        return false;
    }
    return true;
}

 

아래는 예제코드가 포함한 문제입니다.

명령과 쿼리가 혼합됨: isSatisfied는 반복 조건을 검사하면서 동시에 이벤트의 상태를 변경합니다.

부작용 발생: 쿼리처럼 보이지만 내부적으로 이벤트 상태를 변경하므로, 동일한 호출이라도 실행 결과가 달라질 수 있습니다.

책에서도 같은 값 입력으로 다른 결과가 도출되는 것을 예시로 보여주었습니다.

 

따라서 저자는 명령과 쿼리를 분리하고 각각의 역할을 명확히 정의해 해결하는 방법을 소개합니다.

private void reschedule(RecurringSchedule schedule){
    from = LocalDateTime.of(from.toLocalDate().plusDays(daysDistance(schedule)),
            schedule.getFrom());
    duration = schedule.getDuration();
}

private long daysDistance(RecurringSchedule schedule){
    return schedule.getDayOfWeek().getValue() - from.getDayOfWeek().getValue();
}

public boolean isSatisfied(RecurringSchedule schedule){
    if (from.getDayOfWeek() != schedule.getDayOfWeek()
            || !from.toLocalTime().equals(schedule.getFrom())
            || !duration.equals(schedule.getDuration())) {
        return false;
    }
    return true;
}

 

 

 

이렇게 하면 isSatisfied는 상태를 변경하지 않고 단순히 조건 검사만을 수행하게 됩니다.

당연히 반복 호출해도 실행 결과에는 변화가 없을것입니다.

 

저자는 복잡한 소프트웨어에서 명령과 쿼리가 혼합된 메서드는 버그를 양산할 가능성이 크다고 했습니다.

설계 단계에서부터 이 원칙을 염두한다면 더 효율적인 설계가 가능하겠습니다.