TIL

섹션 5 탬플릿

다시보는 OCP

탬플릿

WebApiExRateProvider 리팩터링

public class WebApiExRateProvider extends ExRateProvider {
  @Override
  BigDecimal getExRate(String currency) throws IOException {
    // 네트워크 호출을 통해 환율을 가져오는 로직
    // ...
    return exRate;
  }
}

변하는 코드 분리하기 - 메서드 추출

public class WebApiExRateProvider extends ExRateProvider {
  @Override
  BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    
    URI uri;
    uri = // 1. URI를 준비하고 예외 처리를 위한 작업을 하는 코드
    
    try {
      // 2. API를 실행하고 서버로부터 받은 응답을 가져오는 코드
    } catch (IOException e) {
      throw new RuntimeException(e)
    }
    
    try {
      // 3. JSON 문자열을 파싱하고 필요한 환율 정보를 추출하는 코드
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e)
    }
    
    return exRate;
  }
}
public class WebApiExRateProvider extends ExRateProvider {
  @Override
  BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    URI uri;
    uri = // 1. URI를 준비하고 예외 처리를 위한 작업을 하는 코드
    
    String response;
    try {
      response = executeApi(uri);
    } catch (IOException e) {
      throw new RuntimeException(e)
    }
    
    try {
      return extractExRate(response)
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e)
    }
  }
  
  private BigDecimal extractExRate(String response) throws JsonProcessingException {
    return // 3. JSON 문자열을 파싱하고 필요한 환율 정보를 추출하는 코드
  }
  
  private String executeApi(URI uri) throws IOException {
    return // 2. API를 실행하고 서버로부터 받은 응답을 가져오는 코드
  }
}
public class WebApiExRateProvider extends ExRateProvider {
  @Override
  BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    
    return runApiForExRate(url);
  }
  
  private BigDecimal runApiForExRate(String url) {
    // 변하지 않는 코드
  }
  
  private BigDecimal extractExRate(String response) throws JsonProcessingException {
    return // 3. JSON 문자열을 파싱하고 필요한 환율 정보를 추출하는 코드
  }
  
  private String executeApi(URI uri) throws IOException {
    return // 2. API를 실행하고 서버로부터 받은 응답을 가져오는 코드
  }
}

인터페이스 도입과 클래스 분리

ApiExecutor

public interface ApiExecutor {
  String execute(URI uri) throws IOException;
}
public class SimpleApiExecutor implements ApiExecutor {
  @Override
  public String execute(URI uri) throws IOException {
    // ...
  }
}

콜백과 메소드 주입

public class WebApiExRateProvider extends ExRateProvider {
  @Override
  public BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    
    return runApiForExRate(url, new SimpleApiExecutor());
  }
  
  private BigDecimal runApiForExRate(String url, ApiExecutor apiExecutor) {
    // ...
    String response;
    try {
      response = apiExecutor.execute(uri);
    } catch (IOException e) {
      throw new RuntimeException(e)
    }
    // ...
  }
  //...
}
public class WebApiExRateProvider extends ExRateProvider {
  @Override
  public BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    
    return runApiForExRate(url, new SimpleApiExecutor(), new ErApiExRateExtractor());
  }
  
  private BigDecimal runApiForExRate(String url, ApiExecutor apiExecutor, ExRateExtractor extractor) {
    // 변하지 않는 탬플릿에서 변하는 콜백을 전달 받아 호출
  }
}

ApiTemplate 분리

public class ApiTemplate {
  public BigDecimal getExRate(String url, ApiExecutor apiExecutor, ExRateExtractor extractor) {
    URI uri;
    try {
      uri = new URI(url);
    } catch(URISyntaxException e) {
      throw new RuntimeException(e);
    }
    
    String response;
    try {
      response = apiExecutor.execute(uri);
    } catch (IOException e) {
      throw new RuntimeException(e)
    }
    
    try {
      return extractor.extract(response)
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e)
    }
  }
}
public class WebApiExRateProvider extends ExRateProvider {
  private final ApiTemplate apiTemplate = new ApiTemplate();
  
  @Override
  public BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    
    return apiTemplate.getExRate(url, new SimpleApiExecutor(), new ErApiExRateExtractor());
  }
}
public class WebApiExRateProvider extends ExRateProvider {
  private final ApiTemplate apiTemplate = new ApiTemplate();
  
  @Override
  public BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    
    return apiTemplate.getExRate(url, new HttpClientApiExecutor(), new ErApiExRateExtractor());
  }
}

디폴트 콜백과 탬플릿 빈

대폴트 콜백

public class ApiTemplate {
  private final ApiExecutor apiExecutor;
  private final ExRateExtractor exRateExtractor;
  
  public ApiTemplate() {
    this.apiExecutor = new HttpClientApiExecutor();
    this.exRateExtractor = new ErApiExtractor();
  }
  
  public BigDecimal getExRate(String url) {
    // ...
  }

  public BigDecimal getExRate(String url, ApiExecutor apiExecutor, ExRateExtractor extractor) {
    // ...
  }
}
public class WebApiExRateProvider extends ExRateProvider {
  private final ApiTemplate apiTemplate = new ApiTemplate();
  
  @Override
  public BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    
    return apiTemplate.getExRate(url);
  }
}

탬플릿 빈

@Configuration
public class ObjectFactory {
  // ...
  @Bean
  public ExRateProvider exRateProvider() {
    return new WebApiExRateProvider(apiTemplate());
  }
  
  @Bean
  public ApiTemplate apiTemplate() {
    return new ApiTemplate();
  }
}
public class WebApiExRateProvider extends ExRateProvider {
  private final ApiTemplate apiTemplate;
  
  public WebApiExRateProvider(ApiTemplate apiTemplate) {
    this.apiTemplate = apiTemplate;
  }
  
  @Override
  public BigDecimal getExRate(String currency) {
    String url = "https://open.er-api.com/v6/latest/" + currency;
    
    return apiTemplate.getExRate(url);
  }
}

디폴트 콜백 유연하게 세팅

public class ApiTemplate {
  private final ApiExecutor apiExecutor;
  private final ExRateExtractor exRateExtractor;
  
  public ApiTemplate() {
    this.apiExecutor = new HttpClientApiExecutor();
    this.exRateExtractor = new ErApiExtractor();
  }
  
  public ApiTemplate(ApiExecutor apiExecutor, ExRateExtractor extractor) {
    this.apiExecutor = apiExecutor;
    this.exRateExtractor = extractor;
  }
  
  // ...
}