어댑터 패턴(Adaptor Pattern)

Minjae An·2023년 11월 30일
0

설계패턴

목록 보기
3/7

Adaptor Pattern

어댑터 패턴이란 이름 그대로 클래스가 어댑터로서 사용되는 구조 패턴이다.

어댑터는 서로 호환이 되지 않는 단자를 호환시켜 작동할 수 있게끔 하는 역할을
수행한다.

이와 같이 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들을 함께
작동할 수 있도록 변환
하는 역할을 수행하는 행동 패턴이라고 볼 수 있다.
예를 들어 기존 시스템에 새로운 3rd-party 라이브러리를 추가하고 싶거나, 레거시
인터페이스를 새로운 인터페이스로 교체하는 경우에 어댑터 패턴을 사용하면
코드의 재사용성을 높일 수 있다.

즉, 어댑터란 이미 구축되어 있는 것에 새로운 어떤 것을 사용할 때 양쪽 간의 호환성을
유지해 주기 위해 사용하는 것으로서, 기존 시스템에서 새로운 업체에서 제공하는 기능을
사용하려고 할 때 서로 간의 인터페이스를 어댑터를 통해 일치시켜 호환성 및 신규
기능 확장
을 할 수 있다고 보면 된다.

어댑터가 레거시 인터페이스를 감싸서 새로운 인터페이스로 변환하기 때문에 Wrapper
패턴이라고도 불린다.

어댑터 패턴 구조

  • 합성된 멤버에게 위임을 통한 어댑터 패턴

  • 자신의 수행할 일을 인스턴스 멤버의 메서드에 전달하여 목적을 달성하는 것을
    위임이라고 한다.

  • 합성을 활용했기 때문에 런타임 중에 Adaptee가 결정되어 유연하다.

  • Adaptee(Service) : 어댑터 대상 객체, 기존 시스템 / 외부 시스템 / 써드파티 라이브러리

  • Target(Client interface) : Adapter가 구현하는 인터페이스

  • Adapter : ClientAdaptee(Service) 중간에서 호환성이 없는 둘을
    연결시켜주는 역할을 담당
    - Object Adapter 방식에선 합성을 이용해 구현
    - Adaptee(Service)를 따로 인스턴스 멤버로 설정하고 위임을 통해 동작을
    매치시킨다.

  • Client : 기존 시스템을 어댑터를 통해 이용하려는 주체, Client Interface를
    통하여 Service를 이용할 수 있도록 된다.

클래스 어댑터

  • 클래스 상속을 이용한 어댑터 패턴

  • Adapter(Service)를 상속했기 때문에 따로 객체 구현없이 바로 코드 재사용이
    가능하다.

  • 상속은 대표적으로 기존에 구현된 코드를 재사용하는 방식이지만, 자바에서는 다중
    상속 불가 문제 때문에 전반적으로 권장하지 않는 방법이다.

  • Class Adapter 방식에선 상속을 이용해 구현한다.

  • Existing Class와 Adaptee(Service)를 동시에 implements, extends하여
    구현한다.

어댑터 패턴 특징

패턴 사용 시기

  • 레거시 코드를 사용하고 싶지만 새로운 인터페이스가 레거시 코드와 호환되지 않을 때
  • 이미 만든 것을 재사용하고자 하나 이 재사용 가능한 라이브러리를 수정할 수 없을 때
  • 이미 만들어진 클래스를 새로운 인터페이스(API)에 맞게 개조할때
  • 소프트웨어의 구 버전과 신 버전을 공존시키고 싶을 때

패턴 장점

  • 프로그램의 기본 비즈니스 로직에서 인터페이스 또는 데이터 변환 코드를 분리할 수
    있기 때문에 SRP을 만족한다.
  • 기존 클래스 코드를 건들지 않고 클라이언트 인터페이스를 통해 어댑터와 작동하기
    때문에 OCP을 만족한다.
  • 만일 추가로 필요한 메서드가 있다면 어댑터에 빠르게 만들 수 있다. 만약 버그가
    발생해도 기존의 클래스에는 버그가 없으므로 Adapter 역할의 클래스를 중점적으로
    조사하면 되고, 프로그램 검사도 쉬워진다.

패턴 단점

  • 새로운 인터페이스와 어댑터 클래스 세트를 도입해야 하기 때문에 코드 복잡성이 증가
  • 때로는 직접 Service 클래스를 변경하는 것이 간단할 수 있는 경우가 존재

예제

  • 특정 회사에서 제공하는 Adder(ex. YourAdder)를 사용해야 하고 라이브러리
    형태로 제공되어 수정할 수 없다.
  • 클라이언트 코드도 수정할 수 없다.
  • Adapter 생성

Adder

public interface Adder {
    public int plus(int x, int y);
}

UseAdder

public class UseAdder {
    public int add(Adder adder, int x, int y) {
        return adder.plus(x, y);
    }
}

MyAdder

public class MyAdder implements Adder {
    @Override
    public int plus(int x, int y) {
        return x + y;
    }
}

YourAdder

public class YourAdder {
    public int add3(int x, int y, int z){
        return x+y+z;
    }
}

YourAdderAdapter

public class YourAdderAdapter implements Adder {
    private YourAdder yourAdder;

    public YourAdderAdapter(YourAdder yourAdder) {
        this.yourAdder = yourAdder;
    }

    @Override
    public int plus(int x, int y) {
        return yourAdder.add3(x, y, 0);
    }
}

Adapter 패턴 활용 사례

InputSteramReader

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

콘솔에서 입력을 받을 때 가장 많이 사용하는 클래스중 하나인 BufferedReader
보통 위와 같이 생성자에 InputStreamReader를 인자로 넘겨주어 생성한다.

이 코드는 input을 행하는 System.inBufferedReader객체에 사용하고 싶은데
이 중간을 InputSteramReader가 어댑터 역할을 수행하여 가능케 하는 것으로
해석할 수 있다.

BufferedReader 클래스를 살펴보면 생성자에 Reader타입을 받는다.

하지만, System 클래스의 in필드를 보면 InputStream타입으로 구성되어 있다.

따라서 서로 타입이 맞지 않기 때문에 BufferedReader(System.in)과 같은 형태로
사용할 수 없는 것이다. 그리하여 이 둘을 연결시켜주는 어댑터가 바로 InputStreamReader
클래스이다.

UML로 표현하여 살펴보면 아래와 같은 형태가 된다.

Arrays.asList()

기존의 배열을 리스트로 변환 & 호환 작업을 해주는 Arrays.asList()
어댑터라 간주할 수 있다.

    public static void main(String[] args) {
        String[] arr = {"a", "b", "c"};
        List<String> list = Arrays.asList(arr);
    }

참고

profile
도전을 성과로

0개의 댓글