12장의 Bank.reduce는 Money.dollar(10)을 하드코딩한 가짜다. 이제 진짜로 만든다. $5 + $5는 실제로 두 값을 더해 $10이 나와야 하고, 단일 Money 하나를 환산하면 그대로 자기 값이 나와야 한다.
누가 계산을 책임질까. Bank가 if (source instanceof Sum) ... else ...로 분기할 수도 있다. 하지만 벡은 타입 검사 대신 다형성을 택한다. 환산 방법은 식의 종류가 가장 잘 안다. 그러니 Bank는 식에게 “너를 이 통화로 환산해봐”라고 위임하고, Sum과 Money가 각자 reduce를 구현한다.
사이클: 위임 → Sum.reduce → Money.reduce
interface Expression { Money reduce(String to);}class Bank { Money reduce(Expression source, String to) { return source.reduce(to); }}class Sum implements Expression { Money augend; Money addend; Sum(Money augend, Money addend) { this.augend = augend; this.addend = addend; } public Money reduce(String to) { int amount = augend.amount + addend.amount; return new Money(amount, to); }}// $5 + $5 → Sum.reduce가 10을 계산한다. testSimpleAddition 진짜 초록.// 하지만 Money에는 아직 reduce가 없어 컴파일되지 않는다 — Expression 계약 위반.
instanceof 한 번 없이 두 종류의 식이 각자의 방식으로 환산된다. Bank는 어느 쪽이 왔는지 몰라도 되고, 새로운 식(예: 곱셈)이 생겨도 Bank는 손대지 않는다. 조건 분기를 다형성으로 바꾸는 이 패턴이 Money 예제의 설계를 지탱한다.
Money.reduce가 return this인 게 눈에 띈다. 지금은 통화 변환이 없으니 자기 자신이 곧 답이다. 이 자리는 14장에서 환율이 들어오면 new Money(amount / rate, to)로 자라날 씨앗이다 — 가장 단순한 구현으로 초록을 만들고, 요구가 생기면 그때 일반화한다.
판단 기준: 분기하고 싶을 때 “이 판단을 데이터 자신에게 물어볼 수 있나?”를 먼저 생각한다. 가능하면 조건문 대신 메서드 오버라이드로 옮긴다. 함정: reduce를 Sum에만 만들고 Money를 빠뜨리면 인터페이스 계약이 깨진다. 인터페이스에 메서드를 추가하면 모든 구현체가 응답해야 컴파일된다 — 컴파일러가 빠짐을 잡아준다.
이 장이 남긴 할 일 목록
Bank.reduce(Sum) 진짜로Bank.reduce(Money)- 환율이 다른 통화 환산 — reduce에 환율 적용 → 14장
- 10 (환율 2:1)
다음장으로 14장