dev_공부일지/Spring 6

관심사 분리 2. 상속을 통한 확장

dev_0hoon 2024. 9. 18. 14:19
package com.hellopayment;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.stream.Collectors;

public class PaymentService {

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

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

    private 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);
        BigDecimal exRate = data.rates().get("KRW");
        return exRate;
    }

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

    }

}
  • 메소드를 통해 관심사를 분리했지만, 아직 같은 클래스로 묶여있다. 재사용성을 생각할 때에는 하나의 클래스에 두는 것은 좋지 않다.

👯‍♀️분리의 이유

  1. 환율을 가져오는 정책 등이 바뀌거나 할 때에는 수정 후 다시 빌드를 하곤 해야한다. 그래서 분리를 하려고 한다.
  2. 만약 외부에서 이 로직이 좋으니 라이브러리로 판매하라 라는 요청이 온다면, 문제가 무엇은 jar파일로 컴파일해서 주면서 prepare를 쓰면 된다.
  3. 고객이 환율정보는 자신의 것으로 긁어오고 변환만 해주는 것을 쓰고 싶다. 라는 의견에는 맞지 않는 클래스와 메소드가 되어버린다.

 

이럴 때, 라이브러리 다 뜯어서 다시 컴파일해서 쓰라고 할 순 없다.. 오히려 getExRate라는 것을 수정해서 써라라고 말할 수 있게 상속을 이용할 수 있다. 디자인패턴에도 상속이 포함되어있다.

 

package com.hellopayment;

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

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

    }
}

package com.hellopayment;

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

abstract class PaymentService {

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

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

    abstract BigDecimal getExRate(String currency) throws IOException;

}

PaymentService에는 getExRate라는 추상화 메소드를 넣어준다. prepare라는 메소드(자사의 기술?)은 쓰되 getExRate는 언제든 상속해서 변경 후 사용할 수 있게 된다.

 

package com.hellopayment;

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

public class SimpleExRatePaymentService extends PaymentService{

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

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


package com.hellopayment;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.stream.Collectors;

public class WebApiExRatePaymentService extends PaymentService{
    @Override
    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);
        BigDecimal exRate = data.rates().get("KRW");
        return exRate;
    }
}

 

- PaymentService는 앞으로 변경할 필요가 없다.

- 우리가 WebApiExRatePaymentService에 자사의 기술을 녹이면 된다.

- 만약 getExRate의 내용을 바꾸고 싶다면, 상속받아서 바꿔 사용하면 된다.

- 우리는 이제 신경 쓸 필요 없이 확장성 있는 코드를 제공할 수 있게 된다.

 

단점

- 만약 보너스 환율을 제공하는 상황이 생긴다고 한다. (수시로 바뀌는 정책) 그럼 abstract가 또 늘어나야 할 것이다. 또한 메소드 이름도 바꿔야하고, 그런 경우 상속받는 클래스도 연달아 늘어날 가능성이 높아진다. 상속을 통한 확장은 장기적으로 볼 때 좋아보지 않는다.