❓고민: 데이터 활용 책임을 누구에게 맡길 것인가
방법 1. 내부 데이터 전달
private Rank calculateRankForLotto(Lotto lotto, WinningBalls winningBalls, BonusBall bonusBall) {
// WinningBalls의 내부 데이터를 꺼내서 전달
int sameWinningCount = lotto.matchWith(winningBalls.getLottoNumbers());
...
}
방법 2. 객체 자체 전달
private Rank calculateRankForLotto(Lotto lotto, WinningBalls winningBalls, BonusBall bonusBall) {
// 객체 자체를 전달
int sameWinningCount = lotto.matchWith(winningBalls);
...
}
❗️결론
객체 자체를 전달하는 두 번째 방식이 더 좋습니다. 데이터 활용 책임을 누구에게 맡길 것인가? 이에 대한 대답은 당연히 그 데이터를 가지고 있는 객체
입니다. 저도 알고 있었지만, 위 고민의 시작은 객체자체를 전달하면 Lotto가 WinningBalls와 BonusBall을 알게 되고 Lotto가 이 객체들과 결합이 생기는 거 아닌가? 라는 생각에서 출발했습니다. 그러나 이 생각은 디미터 법칙(Don't Talk to Strangers)을 완전 잘못 이해하고 있는 거였습니다.
- 디미터 법칙에 대한 오해
- 객체 간 최소한의 지식만 공유해야 한다는 원칙을 '객체 의존성 최소화'로 오해
- 메소드가 특정 객체에 종속되면 재사용성이 떨어진다고 잘못 이해
- 객체를 전달하면 결합도가 높아진다는 잘못된 이해
- 디미터 법칙의 진정한 의미
- 최소한만 알아야 한다는 건 "낯선 자에게 말하지 말라"는 원칙을 위한 말임
- 객체의 내부 구조 노출 방지가 핵심
- 객체가 자신의 데이터를 책임지고 관리하도록 하는 것이 목적
// 방법 1: 데이터를 받는 경우
class Lotto {
public int matchWith(List<Integer> numbers) {
// 단순 데이터 비교 로직
}
}
// 방법 2: 객체를 받는 경우
class Lotto {
public int matchWith(WinningBalls winningBalls) {
// 객체 간 협력 로직
}
}
만약 match 메소드가 WinngBalls에만 쓰고 싶지 않다면, 아래와 같이 인터페이스
로 해결하면 됩니다.
interface Matchable {
int matchWith(List<Integer> numbers);
}
class Lotto {
public int matchWith(Matchable winner) {
return winner.matchWith(this.numbers);
}
}
class WinningBalls implements Matchable {
@Override
public int matchWith(List<Integer> numbers) {
// 구현
}
}
Tell, Don’t Ask
위 단계를 통해 이전보다 낫게 만들어줬지만, 객체를 넘겨주는 것만으로는 좋은 코드가 되지 않습니다. 그 객체가 직접 실행하게 만들어야 합니다.
// 안좋은 예시 - 데이터를 직접 꺼내서 처리
public class Lotto {
private List<Integer> numbers;
// 내부 데이터를 외부로 노출하는 getter
public List<Integer> getNumbers() { // 캡슐화 위반
return numbers;
}
public boolean matchWith(WinningNumbers winningNumbers) {
List<Integer> winningNumbersList = winningNumbers.getNumbers(); // 데이터를 꺼내서
return this.numbers.containsAll(winningNumbersList); // 직접 처리
// 좋은 예시 - 객체에게 메시지를 보내 협력
public class Lotto {
private List<Integer> numbers;
public boolean matchWith(WinningNumbers winningNumbers) {
return winningNumbers.contains(this.numbers); // 메시지를 통한 협력
}
}
public class WinningNumbers {
private List<Integer> numbers;
public boolean contains(List<Integer> numbersToCheck) {
// 자신의 데이터를 스스로 처리
return this.numbers.containsAll(numbersToCheck);
}
}
+) 디미터 법칙과 Tell, Don’t Ask는 다르다
// 디미터 법칙 위반
public class Order {
private Customer customer;
public void processOrder() {
// customer -> account -> billingInfo 순으로 객체 탐색 (기차 충돌)
if (customer.getAccount().getBillingInfo().isValidPaymentMethod()) {
// 주문 처리
}
}
}
// 디미터 법칙 준수
public class Order {
private Customer customer;
public void processOrder() {
// customer와만 직접 대화
if (customer.canProcessPayment()) {
// 주문 처리
}
}
}
디미터 법칙을 공부하기 위해 여러 블로그들을 찾아봤는데 두 법칙을 혼용하는 경우가 많았습니다. 물론 둘 다 객체 지향적으로 만들기 위한 좋은 법칙이라는 건 변함이 없기 때문에 문제가 되진 않습니다. 그래도 두 법칙이 뭘 말하고자 하는지, 명확한 의도는 알아야 한다고 생각합니다.
- Tell, Don’t Ask: 다른 객체의 데이터를 물어보지말고 기능을 실행해달라고 말하는 것입니다.
- 다른 객체(B)의 속성을 꺼내지 말고 B의 메소드를 호출하라는 뜻입니다.
- 디미터 법칙: 다른 객체(B)의 상태에 포함되어 있는 또 다른 객체(C)의 메소드를 직접 이용하지 말라는 것입니다.
- Tell, Don’t Ask 법칙을 지킨다면 애초에 디미터 법칙이 지켜지는 것입니다!
객체 지향에 대해 공부하다 보면, 기계적으로 학습할 때가 많았습니다. 이번 기회를 통해 기계적 학습이 아닌 객체지향의 본질을 알고 공부해야겠다는 생각을 할 수 있었습니다.
+) 객체지향의 본질은 객체 분리가 아닌 메시징을 주고받는 객체들의 협력입니다. 이 말이 객체지향을 체험하는데 영향을 줄거라 믿습니다.
'객체지향' 카테고리의 다른 글
🚫 else 금지 이유와 개선 법 (0) | 2024.11.12 |
---|---|
🤔 일급 컬렉션을 써야하는 이유 (0) | 2024.11.12 |
Google Java Style Guide 번역 (1) | 2024.10.15 |
[클린코드] 5 ~ 10장 (0) | 2024.03.04 |
[클린코드] 4. 주석 (0) | 2024.03.04 |