[테스트 주도 개발] 9장_우리가 사는 시간

제목 : 영어에서 시간과 곱하기가 모드 ‘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
// Dollar Class
@Override
String currency() {
return "USD";
}

// Franc Class
@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 Class
Franc(int amount, String currency) {
this.amount = amount;
this.currency = "CHF";
}

이제, 생성자를 호출하는 코드 두 곳이 깨진다.

1
2
3
4
5
6
7
8
9
// Money Class
static Franc franc(int amount) {
return new Franc(amount, null);
}

// Franc Class
Money times(int multiplier) {
return new Franc(amount * multiplier, null);
}

잠깐. 왜 Franc.times 가 팩토리 메서드를 호출하지 않고 생성자를 호출하고 있는거지 ? 지금 이걸 고쳐야하나 ? 아니면 지금 하고 있는 일을 먼저 끝내야 하나 ? 보통 짧은 중단이 필요한 경우에는 이를 흔쾌히 받아들이자.

1
2
3
4
// Franc Class
Money times(int multiplier) {
return Money.franc(amount * multiplier);
}

이제 팩토리 메서드가 ‘CHF’ 를 전달할 수 있다.

1
2
3
4
5
// Money Class
static Franc franc(int amount) {
return new Franc(amount, "CHF");
}

그리고, 마지막으로 인자를 인스턴스 변수에 할당할 수 있다.

1
2
3
4
5
//Franc Class
Franc(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}

Dollar 도 이와 유사하게 수정하자.

1
2
3
4
// Money Class
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
Money(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
1
2
3
4
// Dollar
Dollar(int amount, String currency) {
super(amount, currency);
}
1
2
3
4
// Franc
Franc(int amount, String currency) {
super(amount, currency);
}

테스트 주도 개발 <켄트 벡>

Comments