Adapter Pattern을 시나리오를 통해 느껴보자!

강상우·2021년 7월 23일
1

DesignPattern

목록 보기
2/2
post-thumbnail

개요

한가지 상황을 이야기하며 포스팅을 시작해보려합니다! 시나리오를 위해 약간의 오버스러운 점이 있을 수 있으니 양해하고 읽어주시면 감사드리겠습니다..🤗
만약, 계산기 역할을 하는 라이브러리를 사용하는 서비스가 있다고 해보겠습니다. 이 계산기 라이브러리는 StandardCalculator라는 1900년대의 가장 큰 IT기업에서 정한 계산기 인터페이스를 구현하는 구현체입니다.

서비스에서는 StandardCalculator의 구현체를 의존하고 있지 않고 StandardCalculator인터페이스에 의존하고 있습니다. DIP 원칙을 잘 지키며 설계를 했습니다.

그런데 StandardCalculator의 구현체들은 한가지 단점이 있습니다. 모두 과거에 만들어진 계산기여서 계산 메소드 호출 전 로딩 시간이 존재합니다.

이에 대응하여 2000년대에 세계IT협회에서 새롭게 AmazingCalculator라는 인터페이스를 만들게 되었습니다. 2000년대의 기업들은 모두 이 새로운 인터페이스의 구현체들을 개발하기 시작했습니다.

마침내, 저희가 속해있는 조직까지 새로운 인터페이스의 구현체를 사용해야하는 상황이 오게되었습니다.

문제 발생

현재 저희가 만들고 있는 애플리케이션은 모든 곳에서 계산기 라이브러리를 사용하고 있습니다.
즉, 인터페이스가 변경되었으므로, DIP원칙을 잘 지키고 있었다고 하더라도 새로운 AmazingCalculator 인터페이스를 바꾸게 되면 모든 코드들이 변경되어지는 상황이 벌어집니다.

코드를 모두 바꾸는 것은 정말 정말 힘들고, 실수가 발생할 여지가 많습니다.

해결 방법에 대한 아이디어

이런 경우 어떻게 해야될까요?
Adapter Pattern을 이야기할 때 항상 나오는 예시를 들어보겠습니다. 저희가 사용하는 모든 가전 제품의 충전기는 국내 전용 플러그 규격(인터페이스)를 사용합니다. 즉, 어떤 시중 플러그(구현체)를 구입하여도 국내에서는 모두 콘센트에 정확히 꽂힙니다.

반면, 아무리 국내 시중 플러그(구현체)가 국내 전용 플러그 규격(인터페이스)을 맞추어 생산된다 하더라도, 유럽의 콘센트에는 꽂히지 않습니다.
유럽의 콘센트는 유럽 전용 플러그 규격(다른 인터페이스)을 사용하니깐요!

이때 저희는 유럽여행을 갈 때 국내 플러그에 유럽 콘센트에 들어갈 수 있는 어댑터를 챙겨갑니다.

이를 통해 국내 표준 플러그 인터페이스의 모양을 계속해서 사용하더라도 어댑터를 이용해 유럽 콘센트에 꽂는 것이 가능해집니다!

어댑터 패턴은 이와 아주 유사합니다
어댑터는 서비스에서 기존의 인터페이스에서 정의한 메소드를 호출하더라도 새로운 인터페이스의 메소드가 아무렇지 않게 호출이 되어야 합니다.

이제 어댑터 클래스를 구현해보겠습니다!

Adapter 클래스 만들기

Adapter는 기존 방식의 참조변수가 참조할 수 있는 기존 시스템의 인터페이스 규격을 가지고 있어야 합니다.
그리고, 이 인터페이스는 새로운 방식의 시스템의 메소드를 호출할 수 있는 연결 고리 역할을 해야 합니다.

즉, StandardCalculator 인터페이스를 의존하던 모든 서비스들의 코드에 변경이 없어야 하지만, 실제로는 StandardCalculator의 구현체로 Adapter가 들어가고 이 Adapter는 AmazingCalculator 인터페이스에 의존하고 있는 모양이 되어야 합니다.

모든 코드

전체 코드를 보며 이제 이해해보겠습니다.
기존에 저희가 개발하고 있다고 했던 애플리케이션의 Service 레이어를 보겠습니다.

public class CalculateService {

  private final StandardCalculator calculator;

  public CalculateService(StandardCalculator calculator) {
    this.calculator = calculator;
  }

  public int plusTwoNums(int num1, int num2) {
    return calculator.plusNums(num1, num2);
  }

  public int minusTwoNums(int num1, int num2) {
    return calculator.minusNums(num1, num2);
  }

  public int multipleTwoNums(int num1, int num2) {
    return calculator.multipleNums(num1, num2);
  }

  public int divideTwoNums(int num1, int num2) {
    return calculator.divideNums(num1, num2);
  }
}

CalculateService는 StandardCalculator 인터페이스에 의존하고 있습니다. 만약 위에서 이야기했던 구현체를 바꾸는 것이 아닌 인터페이스 자체를 변경해야 하는 경우, calculator를 통해 호출하는 모든 메소드를 다 변경시켜야 합니다.

저희는 이런 이유로 어댑터를 만들고, 어댑터에 새로운 인터페이스를 정의했습니다.

// Adapter

public class AmazingAdapter implements StandardCalculator {

  private final AmazingCalculator amazingCalculator;

  public AmazingAdapter(AmazingCalculator amazingCalculator) {
    this.amazingCalculator = amazingCalculator;
  }

  @Override
  public int plusNums(int n1, int n2) {
    return amazingCalculator.plus_two_nums(n1, n2);
  }

  @Override
  public int minusNums(int n1, int n2) {
    return amazingCalculator.minus_two_nums(n1, n2);
  }

  @Override
  public int multipleNums(int n1, int n2) {
    return amazingCalculator.multiple_two_nums(n1, n2);
  }

  @Override
  public int divideNums(int n1, int n2) {
    return amazingCalculator.divide_two_nums(n1, n2);
  }
}

어댑터는 기존의 StandardCalculator 인터페이스의 구현체임과 동시에 AmazingCalculator 인터페이스를 의존하고 있습니다.

그리고, StandardCalculator에 정의된 메소드를 amazingCalculator의 메소드 호출 방식으로 오버라이딩 했습니다.

마지막으로, Adapter 객체를 생성할 때, AmazingCalculator인터페이스를 구현한 TeslaCalculator 객체를 넣어주고, Service가 의존하는 StandardCalculator의 구현체로 Adapter객체를 넣어주었습니다.

마무리

이런 방식으로 우리는 기존의 서비스 코드 변경없이 새로운 인터페이스의 구현체들을 사용할 수 있게 되었습니다.

디자인 패턴은 이렇게 시나리오를 만들며 공부하는 것이 참 즐거운 것 같습니다.

다음 포스팅에는 더 흥미로운 시나리오로 설명해보겠습니다!

해당하는 모든 코드는 여기를 참조해주세요

감사합니다!

profile
https://sangwoo0727.github.io/ 기존 블로그 이전 중입니다.

0개의 댓글