Eternity's Chit-Chat

aeternum.egloos.com



유연한 설계를 위한 패턴과 원리 - 3.리팩토링을 통한 진보 8부 Supple Design

테스트를 통과시키기 위해서는 3가지 메소드를 추가해야 한다. 우선 역수를 구하는 inverse() 메소드와 VALUE OBJECT 동등성을 판단하기 위한 equals() 메소드, 그리고 equals() 메소드를 구현하면 반드시 함께 구현해 줘야 하는 hashCode() 메소드가 그것이다. inverse() 단순히 분모와 분자를 바꾸어 새로운 Ratio 인스턴스를 생성하면 된다. equals() hashCode() 분모와 분자의 값을 사용하여 동등성 비교 해쉬값을 계산한다.

 

Ratio.java

public Ratio inverse() {

  return Ratio.of(denominator, numerator);

}

 

public boolean equals(Object object) {

  if (this == object) {

    return true;

  }

 

  if (!(object instanceof Ratio)) {

    return false;

  }

 

  Ratio other = (Ratio) object;

  return (numerator.equals(other.numerator) &&

    denominator.equals(other.denominator));

}

 

public int hashCode() {

  int result = 17;

  result = 37 * result + numerator.hashCode();

  result = 37 * result + denominator.hashCode();

  return result;

}

 

이제 구현된 Ratio 사용하도록 ChangeBooth 코드를 리팩토링하자. 아니다. 순서가 잘못 됐다. double 타입 대신 구현된 Ratio 사용하여 환율을 비교하도록 ChangeBoothTest 코드를 리팩토링하자.

 

ChangeBoothTest.java

@Test

public void getCorrectExchangeRate() {

  ChangeBooth changeBooth = new ChangeBooth();

  changeBooth.addExchangeRate(Money.USD, Money.KRW, 1027);

  assertEquals(Ratio.of(1027), changeBooth.getExchangeRate(Money.USD, Money.KRW));

}

 

@Test(expected=ExchangeRateNotFoundException.class)

public void exchangeInvalidCurrency() {

  ChangeBooth changeBooth = new ChangeBooth();

  assertEquals(Ratio.of(1027),

    changeBooth.getExchangeRate(Currency.getInstance("CHF"), Money.KRW));

}

 

리팩토링 도중에 기존 코드에 문제가 발생 했는지를 확인할 있는 완벽한 회귀 테스트 집합을 갖추고 있으므로 가벼운 마음으로 리팩토링 하자. addCurrencyPair() 메소드는 double 타입 대신 Ratio 추가한다.

 

ChangeBooth.java

private void addCurrentPair(CurrencyPair pair, double rate) {

  rates.put(pair, Ratio.of(rate));

}

 

getExchangeRate() 메소드는 1 비율로 나눈 값이 아니라 inverse() 메소드의 반환 값을 반환한다.

 

ChangeBooth.java

Ratio getExchangeRate(Currency from, Currency to)
 
throws ExchangeRateNotFoundException {

  if (rates.containsKey(CurrencyPair.with(from, to))) {

    return rates.get(CurrencyPair.with(from, to));

  }

 

  if (rates.containsKey(CurrencyPair.with(from, to).reverse())) {

    return rates.get(CurrencyPair.with(from, to).reverse()).inverse();

  }

 

  throw new ExchangeRateNotFoundException();

}

 

exchangedAmount() 메소드는 Ratio times() 메소드와 toDecimal() 메소드를 사용해서 금액을 계산한다.

 

private BigDecimal exchangedAmount(Money amount, Currency to) {

  return getExchangeRate(amount.getCurrency(),to)

          .times(amount.getAmount())

          .toDecimal(to.getDefaultFractionDigits(), RoundingMode.HALF_EVEN);

}

 

모든 테스트를 돌려보자. 녹색 막대다. 리팩토링이 성공했고 모든 코드가 안전하다는 희망의 신호다. 다음은 TDD 통해 얻은 최종 결과를 다이어그램으로 도식화한 것이다. 비록 설계 회의에서 얻었던 모델에 비해 많은 부분이 변경되었지만 환율이란 통화 간의 비율이며  오직 하나만 존재해야 한다 도메인 내에 포함된 개념을 명확하게 표현하고 있다.

그림 1 리팩토링을 통해 개선된 환전 도메인 모델