TIL

섹션 3. 오브젝트와 의존관계

오브젝트와 의존관계

클래스와 오브젝트

Java에서 오브젝트란 클래스의 인스턴스, 또는 배열이다. 이는 기술적으로는 완벽한 대답

의존관계 (Dependency)

관심사의 분리 (Separation of Concerns, SoC)

public class PaymentService {
  public Payment prepare(Long orderId, String currency, BigDecimal foreignCurrencyAmount) throws IOException {
    // 환율 가져오기
    URL url = new URL("https://open.ir-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");
    
    BigDecimal convertedAmount = foreignCurrencyAmount.multiply(exrate);
    LocalDateTime validUntil = LocalDateTime.now().plusMinutes(30);
    
    return new Payment(orderId, currency, foreignCurrencyAmount, exRate, convertedAmount, validUntil);
  }
}
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 {
    // ...
    return exRate;
  }
}

상속을 통한 확장

abstract 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);
  }
  
   abstract BigDecimal getExRate(String currency) throws IOException {
}
public class WebApiExRatePaymentService extends PaymentService {
  @Override
  BigDecimal getExRate(String currency) throws IOException {
    // ...
    return exRate;
  }
}

클래스의 분리

public class PaymentService {
  private final WebApiExRateProvider 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 class WebApiExRateProvider {
    BigDecimal getExRate(String currency) throws IOException {
    // ...
    return exRate;
  }
public class PaymentService {
  private final SimpleApiExRateProvider exRateProvider; // 클래스 변경
  
  public PaymentService() {
    this.exRateProvider = new SimpleApiExRateProvider(); // 변경!
  }

  public Payment prepare(Long orderId, String currency, BigDecimal foreignCurrencyAmount) throws IOException {
    BigDecimal exRate = exRateProvider.findExRate(currency); // 메서드 이름도 다를 수도!
   // ...
  }
}

인터페이스 도입

public interface ExRateProvider {
  BigDecimal getExRate(String currency) throws IOException
}
public class PaymentService {
  private final ExRateProvider exRateProvider;
  
  public PaymentService() {
    this.exRateProvider = new SimpleApiExRateProvider();
  }

  public Payment prepare(Long orderId, String currency, BigDecimal foreignCurrencyAmount) throws IOException {
    BigDecimal exRate = exRateProvider.getExRate(currency);
   // ...
  }
}

관계 설정 책임의 분리

public class PaymentService {
  private final ExRateProvider exRateProvider;
  
  public PaymentService(ExRateProvider exRateProvider) {
    this.exRateProvider = exRateProvider;
  }

  public Payment prepare(Long orderId, String currency, BigDecimal foreignCurrencyAmount) throws IOException {
    BigDecimal exRate = exRateProvider.getExRate(currency);
   // ...
  }
}
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 ObjectFactory {
  public PaymentService paymentService() {
    return new PaymentService(exRateProvider());
  }
  
  public ExRateProvider exRateProvider() {
    return new WebApiExRateProvider();
  }
}
public class Client {
  public static void main(String[] args) throws IOException {
    ObjectFactory objectFactory = new ObjectFactory();
    PaymentService paymentService = objectFactory.paymentService();
    
    Payment payment = paymentService.prepare(100L, "USD", BigDecimal.valueOf(50.7));
    System.out.println(payment);    
  }
}

원칙과 패턴

개방-폐쇄 원칙 (Open-Closed Principle)

public class PaymentService {
  private final ExRateProvider exRateProvider;
  
  public PaymentService(ExRateProvider exRateProvider) {
    this.exRateProvider = exRateProvider;
  }
  // ...
}

높은 응집도와 낮은 결합도

전략 패턴

제어의 역전

스프링 컨테이너와 의존관계 주입

public class ObjectFactory {
  public PaymentService paymentService() { // 빈
    return new PaymentService(exRateProvider()); // 관계 설정
  }
  
  public ExRateProvider exRateProvider() { // 빈
    return new WebApiExRateProvider();
  }
}
import org.springframework.beans.factory.BeanFactory;

public class Client {
  public static void main(String[] args) throws IOException {
    BeanFactory beanFactory = new BeanFactory();
    PaymentService paymentService = beanFactory.getBean(PaymentService.class);
    
    Payment payment = paymentService.prepare(100L, "USD", BigDecimal.valueOf(50.7));
    System.out.println(payment);    
  }
}
@Configuration
public class ObjectFactory {
  @Bean
  public PaymentService paymentService() {
    return new PaymentService(exRateProvider());
  }
  
  @Bean
  public ExRateProvider exRateProvider() {
    return new WebApiExRateProvider();
  }
}
public class Client {
  public static void main(String[] args) throws IOException {
    BeanFactory beanFactory = new AnnotationConfigApplicationContext(ObjectFactory.class);
    PaymentService paymentService = beanFactory.getBean(PaymentService.class);
    
    Payment payment = paymentService.prepare(100L, "USD", BigDecimal.valueOf(50.7));
    System.out.println(payment);    
  }
}

의존관계 주입 (Dependency Injection)

컨테이너

구성정보를 가져오는 다른 방법

싱글톤 레지스트리

DI와 디자인 패턴

WebApiExRateProvider에 캐시를 도입하려면?

데코레이터 패턴 - 오브젝트에 부가적인 기능/책임을 동적으로 부여하는 패턴

CachedExRateProvider

public class CachedExRateProvider implements ExRateProvider {
  private final ExRateProvider target;
  private BigDecimal cachedExRate;
  
  public CacheExRateProvider(ExRateProvider target) {
    this.target = target;
  }
  
  @Override
  public BigDecimal getExRate(String currency) throws IOException {
    if (cachedExRate == null) {
      cachedExRate = this.target.getExRate(currency);
    }
    
    return cachedExRate;
  }
}
@Configuration
public class ObjectFactory {
  @Bean
  public PaymentService paymentService() {
    return new PaymentService(cachedExRateProvider());
  }
  
  @Bean
  public ExRateProvider cachedExRateProvider() {
    return new CachedExRateProvider(exRateProvider());
  }
  
  @Bean
  public ExRateProvider exRateProvider() {
    return new WebApiExRateProvider();
  }
}

의존성 역전 원칙 (Dependency Inversion Principle)