
앞서 객체지향 코드를 얻기 위해서는 객체가 협력 안에서 수행하는 책임에 초점 맞춰야 된다고 이야기 했다.
그리고 책임은 객체가 수신할 수 있는 메시지에 기반이 된다.
✔️ 메시지: 오퍼레이션명 + 인자
✔️ 메시지 전송: 메시지(오퍼레이션명+인자) + 메시지 수신자
✔️ 오퍼레이션: 객체가 다른 객체에게 제공하는 추상적인 서비스 (퍼블릭 인터페이스에 포함된 메시지)
✔️ 메서드: 메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저 (오퍼레이션에 대한 구현)
✔️ 시그니처: 오퍼레이션명 + 파라미터 목록
🌟 코드 상에서 동일한 이름의 변수에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다 🌟
this 객체 / this 속성 / this 속성의 컬렉션 요소 / 메서드의 매개변수 / 메서드 내에서 생성된 지역 객체에게만 메시지를 전송하라! screeing.getMovie().getDiscountConditions(); // 기차 충돌screeing.calculateFee(audienceCount); // 객체의 응집도 ↑✋🏻 But 무비판적으로 디미터 법칙을 수용하면 오히려 객체의 응집도가 낮아질 수 있으니 주의!
IntStream.of(1, 15, 20, 3, 9).filter(x -> x >. 0).distinct().count();✋🏻 But 모든 상황에서 맹목적으로 위임 메서드를 추가하면 같은 퍼블릭 인터페이스 안에 어울리지 않는 오퍼레이션이 공존하게 된다
public class PeriodCondition implements DiscountCondition {
public boolean isSatisfiedBy(Screening screening) {
// getter()로 인해 "묻지 말고 시켜라" 법칙을 위반한 것처럼 보이는 expression
return screening.getStartTime().getDayOfWeek().equals(dayOfWeek) &&
startTime.compareTo(screening.getStartTime().toLocalTime()) <= 0 &&
endTime.compareTo(screening.getEndTime().toLocalTime());
}
}
public class PeriodCondition implements DiscountCondition {
public boolean isSatisfiedBy(Screening screening) {
return screening.isDiscountable(dayOfWeek, starTime, endTime);
}
}
// Screening이 할인 조건을 판단하는 책임을 떠안게 됨!
// PeriodCondition의 내부 상태(ex. dayOfWeek)가 Screening에게 노출됨!
"수행 방법에 관해서는 언급하지 말고 목적만을 포함하도록 클래스와 인터페이스의 이름을 부여하라"
오퍼레이션의 이름은 협력이라는 문맥을 반영해야 한다 (객체 자신이 아닌 클라이언트의 의도를 표현하는 이름을 가져야 한다)
setTicket(Ticket ticket) → sellTo(Audience audience) / buy(Ticket ticket) / hold(Ticket ticket)
오퍼레이션의 시그니처에는 어떤 조건이 만족되어야만 오퍼레이션을 호출할 수 있고, 어떤 경우에 결과를 반환받을 수 없는지를 표현할 수 없다!
의도를 들어내는 인터페이스 ❌
public class PeriodCondition {
public boolean isSatisfiedByPeriod(Screening screening) { ... }
}
public class SequenceCondition {
public boolean isSatisfiedBySequence(Screening screening) { ... }
}
// 메서드 수준의 캡슐화를 위반!
의도를 들어내는 인터페이스 ⭕️
public class PeriodCondition {
public boolean isSatisfiedBy(Screening screening) { ... }
}
public class SequenceCondition {
public boolean isSatisfiedBy(Screening screening) { ... }
}
메서드가 무엇을 하는지에 초점을 맞추면 클라이언트의 관점에서 동일한 작업을 수행하는 메서드들을 하나의 타입 계층으로 묶을 수 있는 가능성이 커진다 (가독성 ↑ 유연성 ↑)
📖 잠깐! 용어 정리
✔️ 루틴: 어떤 절차를 묶어 호출 가능하도록 이름을 부여한 기능 모듈
✔️ 프로시저: 정해진 절차에 따라 내부의 상태를 변경하는 루틴의 한 종류 (부수효과 ⭕️ / 반환 값 ❌)
✔️ 함수: 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 한 종류 (부수효과 ❌ / 반환 값 ⭕️)
✔️ 참조 투명성: 어떤 표현식 e(f(1) = 3)가 있을 때, 모든 e(f(1))를 e의 값(3)으로 바꾸더라도 결과가 달라지지 않는 특성 (불변성)