dev_공부일지/Spring 6

관심사 분리 3. 인터페이스 도입, 관계설정 책임의 분리

dev_0hoon 2024. 9. 18. 15:03
package com.hellopayment;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;

public class PaymentService {
    private final ExRateProvider exRateProvider;

    public PaymentService() {
        this.exRateProvider = new WebApiExRateProvider();
    }

    public Payment prepare(Long orderId, String currency, BigDecimal foreignCurrencyAmount) throws IOException {
        BigDecimal exRate = exRateProvider.getExRate(currency);
        BigDecimal convertedAmount = foreignCurrencyAmount.multiply(exRate);
        LocalDateTime validUntil = LocalDateTime.now().plusMinutes(30);

        return new Payment(orderId, currency, foreignCurrencyAmount, exRate, convertedAmount, validUntil);
    }

}
public interface ExRateProvider {
    BigDecimal getExRate(String currency) throws IOException;
}


public class SimpleExRateProvider implements ExRateProvider{

    @Override
    public BigDecimal getExRate(String currency) throws IOException {
        if (currency.equals("USD")) return BigDecimal.valueOf(1000);

        throw new IllegalArgumentException("지원되지 않는 통화입니다.");
    }
}

public class WebApiExRateProvider implements ExRateProvider{

    @Override
    public BigDecimal getExRate(String currency) throws IOException {
        URL url = new URL("https://open.er-api.com/v6/latest/" + currency);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String response = br.lines().collect(Collectors.joining());
        br.close();

        ObjectMapper mapper = new ObjectMapper();
        ExRateData data = mapper.readValue(response, ExRateData.class);
        return data.rates().get("KRW");
    }
}

 

위와 같은 코드는 인터페이스를 도입해서 관심사 분리를 이끌어 냈다. 인터페이스만 제공해준다면, 메소드 이름 정도만 맞추는 꼴이 되어버린다. 만약 getExRate의 코드를 변경해도, 클래스를 만들어 사용하는 것은 상관이 없지만

 

PaymentService에서 ExRateProvider의 생성자가 변경되어야 하므로 상속방식보다 오히려 비효율적인 변경이 일어나게 된다.

  • 코드레벨 : PaymentService -> ExRateProvider -> WebApiExRateProvider, SimpleExRateProvider 상태이지만
  • 런타임 레벨  : PaymentService -> WebApiExRateProvider 상태이다.

즉 어디선가는 변경이 일어나야만 한다. 어떤 구현체를 사용해야 할까? 라는 부분은 PaymentService에서 일어나는데 이것을 관계설정 책임이라 한다.

> 어디에서 관계설정을 하느냐에 따라 누가 책임을 가지고 있는지에 대한 이야기이다.

 

여기에서 PaymentService의 코드 변경없이 사용하는 방법이 있다. 관계설정 책임을 분리하는 부분(의존관계를 설정)을 만들어주면 된다.

 

생각보다는 간단한 방식이다.

public class Client {
    public static void main(String[] args) throws IOException {
        PaymentService paymentService = new PaymentService(new WebApiExRateProvider());
        Payment payment = paymentService.prepare(100L, "USD", BigDecimal.valueOf(50.7));
        System.out.println(payment);

    }
}

public class PaymentService {
    private final ExRateProvider exRateProvider;

    public PaymentService(ExRateProvider exRateProvider) {
        this.exRateProvider = exRateProvider;
    }
  • PaymentService쪽에서는 생성자에 인터페이스를 인자로 넘겨주며 변수에 담아준다.
  • Client 쪽에서 사용할 객체를 인자를 넘겨준다.

이렇게 하면 PaymentService에서 코드를 수정할 필요가 없다.