모든 중복을 제거하기 전에는 $5 + $5 테스트에 완료표시를 할수 없다.
-> 코드 중복은 없지만 데이터 중복이 있다.
// Bank
public class Bank {
Money reduce(Expression source, String to) {
return Money.dollar(10);
}
}
@Test
public void testSimpleAddition() {
Money sum = Money.dollar(5).plus(Money.dollar(5));
assertEquals(Money.dollar(10), sum);
Money five = Money.dollar(5);
Expression sum = five.plus(five);
Bank bank = new Bank();
Money reduced = bank.reduce(sum, "USD");
assertEquals(Money.dollar(10), reduced);
}
Bank의 Money.dollar() 부분과 Test의 five.plus 부분이 같다.
중복을 없애기 위해서 우선은 테스트를 아래와 같이 고친다.
@Test
public void testSimpleAddition() {
Money five = Money.dollar(5);
Expression result = five.plus(five);
Sum sum = (Sum) result;
assertEquals(five, sum.augend);
assertEquals(five, sum.added);
}
augend 와 addend 필드를 가지는 Sum 클래스가 필요하다.
public class Sum {
Money augend;
Money addend;
}
Money.plus()는 Sum이 아닌 Money를 반환하게 되어있기 때문에 아래와 같이 고쳐야 한다.
Expression plus(Money addend) {
return new Sum(this, addend);
}
Sum class에 생성자를 추가하고 Expression을 implements 한다.
public class Sum implements Expression{
Money augend;
Money addend;
public Sum(Money augend, Money addend) {
this.augend = augend;
this.addend = addend;
}
}
Test를 다음과 같이 수정할 수 있다.
@Test
public void testSimpleAddition() {
Expression sum = new Sum(Money.dollar(3), Money.dollar(4));
Bank bank = new Bank();
Money result = bank.reduce(sum, "USD");
assertEquals(Money.dollar(7), result);
}
Bank 의 reduce 구문도 이에 맞게 고친다.
public class Bank {
Money reduce(Expression source, String to) {
Sum sum = (Sum) source;
int amount = sum.augend.amount + sum.addend.amount;
return new Money(amount, to);
}
}
하지만 이 코드는 2가지 이유로 지저분한 코드이다.
1. 캐스팅(형변환). 이 코드는 Expression에 대해 작동해야 한다.
2. 공용(public) 필드와 그 필드들에 대한 두 단계에 걸친 레퍼런스.
이를 고치려면 아래와 같이 Bank 와 Sum을 수정해주면 된다.
// Bank
public class Bank {
Money reduce(Expression source, String to) {
Sum sum = (Sum) source;
return sum.reduce(to);
}
}
// Sum
public class Sum implements Expression{
Money augend;
Money addend;
public Sum(Money augend, Money addend) {
this.augend = augend;
this.addend = addend;
}
public Money reduce(String to) {
int amount = augend.amount + addend.amount;
return new Money(amount, to);
}
}
잘 작동하는지 검사하는 테스트를 아래와 같이 작성한다.
@Test
public void testReduceMoney() {
Bank bank = new Bank();
Money result = bank.reduce(Money.dollar(1), "USD");
assertEquals(Money.dollar(1), result);
}
잘 작동하지만 여전히 지저분하다.
Sum은 reduce(String)을 구현하기 때문에 Money도 그것을 구현하도록 바꾼다면 reduce()를 인터페이스에도 추가할 수 있게 된다.
// Money
public Money reduce(String to) {
return this;
}
// Expression
public interface Expression {
Money reduce(String to);
}
// Bank
public class Bank {
Money reduce(Expression source, String to) {
return source.reduce(to);
}
}
우리는 지금까지