Dollar와 Franc에는 이제 생성자밖에 없다. 존재 이유가 사라진 클래스다. 벡은 이런 빈 서브클래스를 “모든 악의 근원”이라 부른다 — 아무 값도 더하지 않으면서 코드를 두 배로 읽게 만드는 구조. 지울 차례다.
지우는 순서가 중요하다. 클래스를 먼저 삭제하면 팩터리가 컴파일되지 않는다. 그러니 참조부터 끊는다. franc 팩터리가 new Franc 대신 new Money를 반환하게 바꾸고, 테스트가 초록이면 dollar도 똑같이 바꾼다. 두 참조가 모두 사라진 뒤에야 클래스를 삭제한다.
사이클: 참조 끊기 → 삭제
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과 동치다. 초록.
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과 동치다. 초록.
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 Money(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); }}// 이제 Dollar/Franc을 참조하는 코드가 어디에도 없다.
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 Money(amount, "USD"); } static Money franc(int amount) { return new Money(amount, "CHF"); } Money times(int multiplier) { return new Money(amount * multiplier, currency); } public boolean equals(Object object) { Money money = (Money) object; return amount == money.amount && currency.equals(money.currency); }}// Dollar와 Franc이 사라졌다. 통화의 차이는 이제 데이터(문자열)일 뿐 타입이 아니다.
두 통화의 차이를 클래스로 표현하던 코드가 문자열 필드 하나로 줄었다. 상속은 강력하지만 값의 차이를 표현하는 데 쓰면 과하다. “Dollar는 Money의 한 종류”가 아니라 “달러는 통화가 USD인 Money”였던 것이다 — is-a가 아니라 has-a였다. 서브클래스가 데이터로 접힐 수 있다면 접는 편이 거의 항상 낫다.
판단 기준: 상속을 제거할 때는 “삭제 → 컴파일 에러 확인”이 아니라 “참조 제거 → 초록 확인 → 삭제”로 간다. 각 단계 끝이 초록이면 언제 멈춰도 안전하다. 함정: 두 팩터리를 한 번에 바꾸고 싶은 유혹. 한 번에 하나씩 바꿔 초록을 확인하면, 만약 깨졌을 때 원인이 방금 바꾼 한 줄로 좁혀진다.
이 장이 남긴 할 일 목록
Dollar/Franc 서브클래스 제거공통 times()- 10 (환율 2:1) — 드디어 더하기로
- 5 = $10 (같은 통화부터) → 12장
다음장으로 12장