변경하려는 인터페이스가 호환이 되지 않는 건에 대하여(어댑터 패턴)

변경하려는 인터페이스가 호환이 되지 않는다…

문제 상황

통합 검색 기능이 이미 DB를 이용하여 구현되어 있다고 가정하자. 그래서 현재 다음과 같은 구조로 되어있는데

게시글 수가 폭발적으로 증가하면서 SQL을 이용한 like 검색 속도 성능에 문제가 발생하기 시작했다. 그래서 해당 문제를 해결하기 위해 FS라는 오픈 소스 검색 서버를 도입하기로 했다. 문서를 보니 FSClient 모듈을 사용하면 FS와 쉽게 연동할 수 있다고 한다.

그런데 문제는 FSClient에서 제공하는 인터페이스와 현재 사용하고 있는 SearchService 인터페이스가 맞지 않는 것이다. 현재 수많은 클래스에서 SearchService를 사용하고 있기 때문에 SearchService 대신 FSClient를 사용하도록 변경하는 것은 많은 비용이 들기 때문에 어렵다.

이 문제를 어떻게 해결해볼 수 있을까?

어댑터 패턴(Adapter Pattern)

클라이언트가 요구하는 인터페이스와 재사용하려는 모듈의 인터페이스가 호환되지 않을 때 사용할 수 있는 패턴이 바로 어댑터 패턴이다. 이 어댑터 패턴을 적용하면 다음과 같은 구조가 되게 된다.

SearchServiceFSAdapater 클래스는 FSClient를 SearchService 인터페이스에 맞춰 주는 책임을 갖는다. SearchServiceFSAdapter의 search() 메서드는 FSClient 객체를 실행하고 그 결과를 SearchService 인터페이스에 맞는 리턴 타입으로 변환 해준다. 코드로 한번 보자

public class SearchServiceFSAdapter implements SearchService {
    private FSClient fsClient = new FSClinet();

    public SearchResult search(String keyword) {
        FSQuery fsQuery = new FSQuery(keyword);                 //(1)
        QueryResponse queryResponse = fsClient.query(fsQuery);  //(2)
        SearchResult result = convertToResult(queryResponse);   //(3)
        return result;
    }
}

private SearchResult convertToResult(QueryResponse queryResponse) {
    ... //queryResponse에서 -> SearchResult로 만들어 주기 위한 코드
    List<SearchDocument> docs = ...
    return new SearchResult(docs);
}
  1. keyword를 FSClient가 요구하는 형식으로 변환
  2. FSClient의 query() 메서드를 실행
  3. FSClient의 결과를 SearchResult로 변환

이렇게 하면 Client 부분의 코드 수정 없이 DB 기반 통합 검색에서 FSClient를 이용한 통합 검색으로 구현을 변경할 수 있게 되었다.

방금 위처럼 구현한 방법을 객체 위임 방식 어댑터라고 한다. 또한, 상속을 이용해서 구현할 수도 있는데 이를 클래스 상속 방식 어댑터라고 한다. 상속으로도 한번 구현해 보자

public class SearchServiceFSAdapter extends FSClient {

    public SearchResult search(String keyword) {
        FSQuery fsQuery = new FSQuery(keyword);
        QueryResponse queryResponse = super.query(fsQuery);
        SearchResult result = convertToResult(queryResponse);
        return result;
    }
}

private SearchResult convertToResult(QueryResponse queryResponse) {
    ... //위와 같음
}

코드가 크게 달라진 부분은 없고 단지 FSClient에 정의된 메서드를 호출하는 방식으로 코드를 작성하게 된다.

적용된 예시

어댑터 패턴이 적용된 예로는 SLF4J라는 로깅 API가 있다. SLF4J는 단일 로깅 API를 사용하면서 자바 로깅, log4j, LogBack 등 다양한 로깅 프레임워크를 유동적으로 사용할 수 있도록 해주는데, 이때 SLF4J가 제공하는 인터페이스와 각 로깅 프레임워크를 맞춰주기 위해 어댑터 패턴을 사용하고 있다.

어댑터 패턴을 사용하면 개방 폐쇄 원칙을 따를 수 있도록 도와준다. 만약 로깅 프레임워크를 Logback으로 교체하고 싶다면 Logback을 slf4j-api 패키지의 Logger로 맞춰 주는 새로운 어댑터만 구현해 주면 되는데 이때, slf4j-api 패키지의 Logger를 사용하는 코드는 전혀 영향을 받지 않는다.


참고:

*틀린 부분이 있으면 언제든지 말씀해 주시면 공부해서 수정하겠습니다.


© 2022. All rights reserved.

Powered by 애송이