Dollar와 Franc에는 이제 생성자밖에 없다. 존재 이유가 사라진 클래스다. 벡은 이런 빈 서브클래스를 “모든 악의 근원”이라 부른다 — 아무 값도 더하지 않으면서 코드를 두 배로 읽게 만드는 구조. 지울 차례다.

지우는 순서가 중요하다. 클래스를 먼저 삭제하면 팩터리가 컴파일되지 않는다. 그러니 참조부터 끊는다. franc 팩터리가 new Franc 대신 new Money를 반환하게 바꾸고, 테스트가 초록이면 dollar도 똑같이 바꾼다. 두 참조가 모두 사라진 뒤에야 클래스를 삭제한다.

사이클: 참조 끊기 → 삭제

Refactoring Step franc 팩터리가 new Money를 반환하도록
1/3
class Money {    protected int amount;    protected String currency;    Money(int amount, String currency) {        this.amount = amount;        this.currency = currency;    }    static Money dollar(int amount) { return new Dollar(amount, "USD"); }    static Money franc(int amount) { return new Money(amount, "CHF"); }    Money times(int multiplier) {        return new Money(amount * multiplier, currency);    }}class Dollar extends Money {    Dollar(int amount, String currency) { super(amount, currency); }}class Franc extends Money {    Franc(int amount, String currency) { super(amount, currency); }}// equals가 통화로 비교하므로 new Money(5,"CHF")는 옛 Franc과 동치다. 초록.

두 통화의 차이를 클래스로 표현하던 코드가 문자열 필드 하나로 줄었다. 상속은 강력하지만 값의 차이를 표현하는 데 쓰면 과하다. “Dollar는 Money의 한 종류”가 아니라 “달러는 통화가 USD인 Money”였던 것이다 — is-a가 아니라 has-a였다. 서브클래스가 데이터로 접힐 수 있다면 접는 편이 거의 항상 낫다.

판단 기준: 상속을 제거할 때는 “삭제 → 컴파일 에러 확인”이 아니라 “참조 제거 → 초록 확인 → 삭제”로 간다. 각 단계 끝이 초록이면 언제 멈춰도 안전하다. 함정: 두 팩터리를 한 번에 바꾸고 싶은 유혹. 한 번에 하나씩 바꿔 초록을 확인하면, 만약 깨졌을 때 원인이 방금 바꾼 한 줄로 좁혀진다.

이 장이 남긴 할 일 목록

  • Dollar/Franc 서브클래스 제거
  • 공통 times()
  • 10 (환율 2:1) — 드디어 더하기로
  • 5 = $10 (같은 통화부터) → 12장

다음장으로 12장