Dollar와 Franc의 times는 반환 타입만 다르고 몸통이 거의 같다. 이 중복을 없애려면 클라이언트가 하위 클래스 이름(new Dollar, new Franc)을 직접 부르지 않게 만들어야 한다. 팩토리 메서드 Money.dollar()와 Money.franc()를 도입하면 생성이 Money 뒤로 숨고, 나중에 하위 클래스를 없앨 여지가 생긴다.
팩토리로 생성을 감추고 선언을 끌어올린다
먼저 times 선언을 Money로 올려 추상 메서드로 만든다. 그래야 Money five = Money.dollar(5)라고 선언하고 five.times(2)를 부를 수 있다. 그다음 팩토리 메서드를 추가하고 테스트가 이를 쓰게 바꾼다.
abstract class Money { protected int amount; abstract Money times(int multiplier); // 추상 선언을 부모로 public boolean equals(Object object) { Money money = (Money) object; return amount == money.amount && getClass().equals(money.getClass()); }}class Dollar extends Money { Dollar(int amount) { this.amount = amount; } Money times(int multiplier) { // 반환형 Dollar → Money return new Dollar(amount * multiplier); }}// Franc도 동일하게 Money times(...) { return new Franc(...); }
abstract class Money { protected int amount; abstract Money times(int multiplier); // 추상 선언을 부모로 public boolean equals(Object object) { Money money = (Money) object; return amount == money.amount && getClass().equals(money.getClass()); }}class Dollar extends Money { Dollar(int amount) { this.amount = amount; } Money times(int multiplier) { // 반환형 Dollar → Money return new Dollar(amount * multiplier); }}// Franc도 동일하게 Money times(...) { return new Franc(...); }
abstract class Money { protected int amount; static Money dollar(int amount) { // 생성이 Money 뒤로 숨는다 return new Dollar(amount); } static Money franc(int amount) { return new Franc(amount); } abstract Money times(int multiplier); public boolean equals(Object object) { Money money = (Money) object; return amount == money.amount && getClass().equals(money.getClass()); }}class Dollar extends Money { Dollar(int amount) { this.amount = amount; } Money times(int multiplier) { return Money.dollar(amount * multiplier); // 팩토리로 되돌려 준다 }}
이제 테스트는 assertEquals(Money.dollar(10), Money.dollar(5).times(2))처럼 쓴다. 클라이언트 어디에도 new Dollar가 없다. 생성 방법이 한 곳으로 모였으니, 하위 클래스를 통째로 없애는 큰 변경도 팩토리 안쪽만 고치면 된다.
판단 기준: 팩토리 메서드는 “무엇을 만드는가(달러)“와 “어떤 클래스로 만드는가(Dollar)“를 분리한다. 클라이언트가 후자를 모를수록 내부 구조를 바꿀 자유가 커진다. 함정: 팩토리를 도입하자마자 하위 클래스를 없애려 서두르지 말자. 아직 times가 하위 클래스마다 다르므로 Dollar/Franc은 필요하다. 통화를 데이터로 옮겨야 비로소 하나로 합칠 수 있다 → 9장의 과제다.
이 장이 남긴 할 일 목록
times 중복 (선언을 Money로 올림)- Dollar/Franc의 times 구현이 아직 남아 있다
- 통화 개념 — Franc/Dollar를 데이터로 구분하기 → 9장
equals(null),equals(다른 객체)처리- 10 (환율 2:1)
다음장으로 9장