어댑터 패턴(Adapter Pattern)

전홍영·2024년 2월 28일
0

디자인 패턴

목록 보기
7/12

어댑터 패턴?

어댑터(Adpater)는 호환되지 않는 인터페이스를 가진 객체들이 협업을 할 수 있도록 하는 구조적 디자인 패턴이다. 우리의 일상에서 어댑터라 하면 해외에 가면 우리가 전기를 사용할 때 한국에서는 220v를 사용하지만 어느 나라는 110v를 사용한다 그래서 우리는 전기 변환기를 가지고 간다. 따라서 어댑터 패턴도 이러한 의미로 어떠한 객체를 다른 객체와 호환시키기 위해서 사용하는 패턴이라고 생각하면 될것이다.

구조

어댑터 패턴에는 두가지 패턴 방법으로 나뉘는데, 기존 시스템의 클래스를 상속해서 호환 작업을 해주냐, 또는 합성해서 호환 작업을 해주냐에 따라 나뉘게 된다. 그러나 상속을 이용한 호환은 다중 상속이 가능한 언어에서만 가능하고 Java에서는 합성을 이용한 객체 어댑터 패턴만 가능하다.

Client

프로그램의 기존 비즈니스 로직을 포함하는 클래스이다.

Client Interface

어댑터가 구현하는 인터페이스이다.

Adapter

Client와 Adaptee(Service) 중간에서 호환성이 없는 둘을 연결 시켜주는 역할을 담당한다.

Service

일반적으로 내부의 서비스라기 보다는 타사의 라이브러리 또는 레거시의 유용한 클래스를 의미한다. 클라이언트는 서비스 클래스를 직접 사용할 수 없다. 왜냐하면 어댑터 패턴은 호환이 되지 않는 인터페이스를 호환이 되려고 사용하는 것인데 클라이언트가 서비스 클래스를 사용할 수 있다면 굳이 어댑터가 필요하겠는가? 따라서 서비스 클래스는 호한되지 않는 데이터 타입, 인터페이스를 가지고 있기 때문에 어댑터가 필요하다.

특징

만약 어댑터가 없이 클라이언트가 바로 서비스와 호환되도록 하는 코드를 클라이언트의 클래스의 넣게 되면 어떤 문제가 생길까?
예를 들어보자 Spring을 통해 개발을 할 때 DB을 사용하기 위해서 해당 DB에 해당하는 Driver가 필요할 것이다. 예를 들어 MySql을 사용하기 위해서는 MySql Driver, NoSql를 사용할 때는 NoSql Driver, JDBC를 사용할 때는 JDBC Driver 이렇게 스프링 어플리케이션과 DB를 호환시켜줄 Driver가 바로 어댑터 역할을 하는 것이다.

따라서 그림을 보면 알 수 있듯이 클라이언트는 Spring Boot이고 Client Interface가 Driver 인터페이스이다. 그리고 어댑터가 Driver를 구현한 구현체들이라고 할 수 있다.

예제 코드로 보는 어댑터 패턴

추상적으로 예시코드를 작성하는 것보다는 어댑터는 구체적인 상황을 예시로 구현해보았다. 레거시 시스템은 SOAP API만을 허용하고 내가 개발하는 시스템에서는 REST API을 사용한다고 한다고 가정하자. 그러면 나는 레거시 시스템에 데이터를 전달하거나 받을때 SOAP API를 통해 주고 받아야한다. 이러한 SOAP를 REST API로 변환하는 Adapter를 구현해보자.

Client

public class Client {
    private final RestApi restApi;

    public Client(RestApi restApi) {
        this.restApi = restApi;
    }

    public void execute() {
        restApi.get("/api");
    }
}

클라이언트는 Rest Api를 이용하여 get 요청을 보내는 비즈니스 로직을 가지고 있다.

Client Interface

public interface RestApi {
    void get(String url);
}

클라이언트는 Rest Api라는 인터페이스에 의존하고 있다.

Service

public class SoapApi {
    public void call(String endpoint, String action, String message) {
        System.out.println("SOAP API is called");
        System.out.println("Endpoint: " + endpoint + ", Action: " + action + ", Message: " + message);
    }
}

레거시 시스템은 SOAP API를 사용하고 있다.

Adapter

public class SoapToRestAdapter implements RestApi {
    private final SoapApi soapApi;

    public SoapToRestAdapter(SoapApi soapApi) {
        this.soapApi = soapApi;
    }

    public void get(String url) {
        soapApi.call(url, "GET", "");
    }
}

이렇게 Client와 Serivce가 서로 다른 인터페이스를 사용하고 있어 이 둘을 호환시켜줄 어댑터를 생성하여 사용한다. 이 어댑터는 Client Interface를 합성하여 사용한여 REST API로 클라이언트가 요청한 것을 SOAP API로 변환하여준다.

Result

@Test
void 어댑터_패턴_테스트() {
    SoapApi soapApi = new SoapApi();
    RestApi adapter = new SoapToRestAdapter(soapApi);
    Client client = new Client(adapter);
    client.execute();
}
//결과
SOAP API is called
Endpoint: /api, Action: GET, Message: 

이렇게 결과로 보면 Client가 호출한 결과가 SOAP API로 변환된 것을 볼 수 있다.

예제 코드

장단점

장점

장점은 기존의 Client 코드와 Service 코드를 손상시키지 않고 클라이언트 인터페이스를 통해 어댑터를 작동하게 하여 OCP(개방 폐쇠 원칙)을 위반하지 않고 코드를 호환할 수 있다는 점이 장점이다. 또한 비즈니스 로직에 추가적인 변환 작업이 추가되지 않고 인터페이스를 통해 책임을 분리함으로서 SRP(단일 책임 원칙)을 지킨다고 할 수 있다.

단점

다수의 새로운 인터페이스와 클래스를 도입해야 함으로 콛의 복잡성이 올라간다. 때로는 이러한 어댑터 패턴의 도입 보다는 Service의 클래스의 코드를 수정하는 것이 더 적합하고 간단한 작업이 될 수 있다.

참조 : https://refactoring.guru/ko/design-patterns/adapter

profile
Don't watch the clock; do what it does. Keep going.

0개의 댓글