text/Java

어댑터 패턴(Adapter pattern) 간단 정리

hoonzii 2023. 3. 10. 12:26
반응형

내가 이해한 어댑터 패턴 간단 정리 (tl;dr)

  • 사용하고자 하는 모듈과 실제 모듈 간의 호환이 되지 않을 때 사용하는 디자인 패턴 중 하나이다.
  • 어댑터 클래스를 구현해 기존 모듈을 상속, 혹은 주입받아 기존 모듈의 동작 & 호환되어야 하는 동작을 adapt 시킨다.

 

 

아래는 내가 다른 정리글들을 보면서 나눈 구분들

 

상황에 따른 구분

먼저 가정이 필요하다. [사용해야 하는 모듈은 내가 개발하는 곳 이외에도 사용처가 많아 수정이 불가능 하다!!]

  1. 기존 모듈 존재 but 모듈 수정 불가 so, 새로운 로직이 필요할 때
  2. 기존 모듈 존재 x (아직 미완성) but 모듈 수정 불가 so, 새로운 로직이 필요할 때

*여기서 새로운 로직이란?

  • 확장이 필요 (할 수도) 할 때-> new 함수 생성
  • 기존 모듈의 기능이 전체 다 필요 없을 때 -> remove (사용 안 하는 방식으로)
  • 기존 모듈의 결과를 재 가공이 필요할 때 -> update

먼저 a 경우에 대해서 살펴보자.

샘플 코드는 이전 글

전략 패턴(strategy pattern) 간단 정리에 사용했던 전략 패턴 코드를 사용한다.

상황을 그림으로 보자면

Strategy를 구현하는 실제 클래스들이 존재하고 결과 값들은 int로 형식이 고정되어 있다.

 

public class mainClass {
    public static void main(String[] args) {
        Context context = new Context();
        context.setStrategy(new AddOperation()); // 연산에 대해 정의
        System.out.println("1 + 2 = "+context.doOperation(1,2)); // 해당 연산을 수행
    }
}

 

이런 상황에서 새롭게 구현해야 하는 모듈이 생겼다고 가정하자. 

그때 누군가 어? 그거 이미 누가 만들어 놓았어요!!

해서 허겁지겁 확인해 보자 근데...

// 기존의 모듈이 존재, 함수 이름도 받는 인자도, 결과 형식도 다르다...!
public class ExternalDivibeOperation {
    public double ExternalDoOperaion(double a, double b) {
        if(b == 0) {
            System.out.println("not divide by ZERO!");
            return 0.0;
        }
        return a / b;
    }
}

 

나누기 연산을 하는 모듈이 이미 존재하지만... 받는 인자값도.... 연산의 결과도.... int가 아니라 double 일 때!

어떻게 해야 할까에 대한 조상들의 고민의 결과, 어댑터 패턴(adapter pattern)이 등장했다.

 

중간에 추가된 divideAdapter의 경우, 기존의 Strategy를 implement 하면서

Context에서 사용하는데 문제없게끔 구현이 되고,

DivideAdapter 내부엔 나누기를 위한 외부 모듈(ExternalDivideOperation)을 주입받아 사용한다.

doOperation 함수에선 외부 모듈의 기능을 수정해 사용, Strategy에 선언된 포맷에 맞게 재가공이 가능하다.

public class DivideAdapter implements Strategy{

    private ExternalDivibeOperation externalDivibeOperation;

    public DivideAdapter(ExternalDivibeOperation externalDivibeOperation) {
        this.externalDivibeOperation = externalDivibeOperation;
    }
    @Override
    public int doOperation(int a, int b) {
        double a_double = a * 1.0;
        double b_double = b * 1.0;
        double result = externalDivibeOperation.ExternalDoOperaion(a_double, b_double);
        return (int) result;
    }
}

결과를 살펴보자면,

public class mainClass {
    public static void main(String[] args) {
        System.out.println("ExternalDivideOperation 사용시");
        ExternalDivibeOperation operation = new ExternalDivibeOperation();
        System.out.println("10 / 3 = "+operation.ExternalDoOperaion(10,3));

        System.out.println("기존 포맷에 맞춰 사용시");
        Context context = new Context();
        context.setStrategy(new DivideAdapter(new ExternalDivibeOperation()));
        System.out.println("10 / 3 = "+context.doOperation(10, 3));
    }
}

그럼 위 b의 경우를 살펴보자.

새로운 모듈을 사용할 건데 아직 존재하지 않을 때 (아직 개발 중이거나, 계획 중에 있는 모듈... 훈지님 언제까지 될까요?

없는데 어떻게 해요?

 

그럴 때는 이전 프록시 패턴

프록시 패턴(Proxy pattern) 간단 정리처럼 프록시를 만들어 준다.

public class FutureOperationProxy implements Strategy{
    @Override
    public int doOperation(int a, int b) {
        System.out.println("not yet exists");
        return 0;
    }
}

public class mainClass {
    public static void main(String[] args) {
        Context context = new Context();
        context.setStrategy(new FutureOperationProxy());
        System.out.println("1 ? 3 = "+context.doOperation(1, 3));
    }
}

 

구현에 따른 구분

합성으로 구현

- 어댑터 내 외부 객체 타입 선언

- 생성자 생성 시 해당 객체 주입

- 함수구현

public class DivideAdapter implements Strategy{

    private ExternalDivibeOperation externalDivibeOperation;

    public DivideAdapter(ExternalDivibeOperation externalDivibeOperation) {
        this.externalDivibeOperation = externalDivibeOperation;
    }
    @Override
    public int doOperation(int a, int b) {
        double a_double = a * 1.0;
        double b_double = b * 1.0;
        double result = externalDivibeOperation.ExternalDoOperaion(a_double, b_double);
        return (int) result;
    }
}

 

상속으로 구현

- 함수 구현시 super.method + 원하는 동작으로 구현

public class DivideAdapterVer2 extends ExternalDivibeOperation implements Strategy{
    @Override
    public int doOperation(int a, int b) {

        double double_a = a * 1.0;
        double double_b = b * 1.0;

        double result = super.ExternalDoOperaion(double_a, double_b);

        return (int) result;
    }
}

 

장단점으로는,

장점

  • 단일 책임 원칙 만족 :)
  • 개방/폐쇄 원칙 만족 :)

단점

  • 딱 봐도 인터페이스랑 어댑터 클래스를 오지게 만들어야 하지 않을까? (그래도 클래스 다 까서 코드 고치는 거나, 호환 안돼서 안 쓰는 것보단 낫지)

 

참고

반응형