제목 : 영어에서 시간과 곱하기가 모드 ‘times’ 라는 점에서 착안한 말장난이다. 통화 개념을 어떻게 테스트하길 원하는가 ? 통화를 표현하기 위한 복잡한 객체를 만들 수도 있다. 그리고, 그 객체들이 필요한 만큼만 만들어지도록 하기 위해 flyweight factories 경량 팩토리를 사용할 수 있을 것이다. 하지만, 당분간 대신 문자열을 쓰자.
1 2 3 4 5 6 @Test void testCurrency () { assertEquals("USD" , Money.dollar(1 ).currency); assertEquals("CHF" , Money.franc(1 ).currency); }
우선, Money 에 currency() 메서드를 선언하자.
1 abstract String currency () ;
그리고 이를, 두 하위 클래스에서 구현하자.
1 2 3 4 5 6 7 8 9 10 11 @Override String currency () { return "USD" ; } @Override String currency () { return "CHF" ; }
우린 두 클래스를 모두. 포함할 수 있는 동일한 구현을 원한다. 통화를 인스턴스 변수에 저장하고, 메서드에서는 그냥 그걸 반환하게 할 수 있을 것이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Franc extends Money { private String currency; Franc(int amount) { this .amount = amount; currency = "CHF" ; } Money times (int multiplier) { return new Franc(amount * multiplier); } @Override String currency () { return currency; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Dollar extends Money { private String currency; Dollar(int amount) { this .amount = amount; currency = "USD" ; } Money times (int multiplier) { return new Dollar(amount * multiplier); } @Override String currency () { return currency; } }
이제 두 currency() 가 동일하므로 변수 선언과 currency() 구현을 모두 위로 올릴 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 abstract class Money { protected int amount; protected String currency; static Money dollar (int amount) { return new Dollar(amount); } public static Franc franc (int amount) { return new Franc(amount); } String currency () { return currency; } public boolean equals (Object object) { Money money = (Money) object; return amount == money.amount && getClass().equals(money.getClass()); } abstract Money times (int multiplier) ; }
문자열 ‘USD’ 와 ‘CHF’ 를 정적 팩토리 매서드로 옮기면 두 생성자가 동일해진다. 그럼 공통 구현을 만들 수 있다.
1 2 3 4 5 Franc(int amount, String currency) { this .amount = amount; this .currency = "CHF" ; }
이제, 생성자를 호출하는 코드 두 곳이 깨진다.
1 2 3 4 5 6 7 8 9 static Franc franc (int amount) { return new Franc(amount, null ); } Money times (int multiplier) { return new Franc(amount * multiplier, null ); }
잠깐. 왜 Franc.times 가 팩토리 메서드를 호출하지 않고 생성자를 호출하고 있는거지 ? 지금 이걸 고쳐야하나 ? 아니면 지금 하고 있는 일을 먼저 끝내야 하나 ? 보통 짧은 중단이 필요한 경우에는 이를 흔쾌히 받아들이자.
1 2 3 4 Money times (int multiplier) { return Money.franc(amount * multiplier); }
이제 팩토리 메서드가 ‘CHF’ 를 전달할 수 있다.
1 2 3 4 5 static Franc franc (int amount) { return new Franc(amount, "CHF" ); }
그리고, 마지막으로 인자를 인스턴스 변수에 할당할 수 있다.
1 2 3 4 5 Franc(int amount, String currency) { this .amount = amount; this .currency = currency; }
Dollar 도 이와 유사하게 수정하자.
1 2 3 4 static Money dollar (int amount) { return new Dollar(amount, "USD" ); }
1 2 3 4 5 6 7 8 9 10 class Dollar extends Money { Dollar(int amount, String currency) { this .amount = amount; this .currency = currency; } Money times (int multiplier) { return Money.dollar(amount * multiplier); } }
이제 두 생성자가 동일해졌다. 구현을 상위 클래스로 올리자.
1 2 3 4 5 Money(int amount, String currency) { this .amount = amount; this .currency = currency; }
1 2 3 4 Dollar(int amount, String currency) { super (amount, currency); }
1 2 3 4 Franc(int amount, String currency) { super (amount, currency); }
테스트 주도 개발 <켄트 벡>