FireDrago

[객체지향과 디자인패턴] 어댑터 패턴 본문

프로그래밍/디자인패턴

[객체지향과 디자인패턴] 어댑터 패턴

화이용 2024. 9. 23. 12:46

어댑터 패턴

클라이언트가 요구하는 인터페이스와 재사용하려는 모듈의 인터페이스가 일치하지 않을때 사용하는 패턴이다

어댑터 패턴은 클라이언트가 요구하는 인터페이스와 재사용하려는 모듈의 인터페이스가 일치하지 않을 때 이를 중간에서 연결해주는 역할을 합니다. 이를 통해 코드의 수정 없이 기존 코드를 재사용할 수 있게 해주며,

개방폐쇄 원칙(OCP)을 지키는 코드를 구현할 수 있습니다.

TolrClient 검색 서버 도입을 위해 어댑터 패턴을 도입

사용자가 정의한 SearchService 인터페이스에서 SQL 사용의 한계 때문에 Tolr 검색서버를 도입하기로 했다.

그런데 TolrClient의  인터페이스가 SearchService 인터페이스와 달라, 너무 많은 코드의 변경이 필요한 상황이다.

이때 SearchServiceTolrAdapter 같은 어댑터 패턴이 필요하다.

public class SearchServiceTolrAdapter implements SearchService {
    private TolrClient tolrClient = new TolrClient();
    
    public SearchResult search(String keyword) {
        // keyword를 tolrClient가 요구하는 형식으로 변환
        TolrQuery tolrQuery = new TolrQuery(keyword);
        // TolrClient 기능 실행
        QueryResponse response = tolrClient.query(tolrQuery);
        // TolrClient의 결과를 SearchResult로 변환
        SearchResult result = convertToResult(response);
        return result;
    }
    
    private SearchResult convertToResult(QueryResponse response) {
        List<TolrDocument> tolrDocs = response.getDocumentList().getDocuments();
        List<SearchDocument> docs = new ArrayList<SearchDocument>();
        for (TolrDocument tolrDoc : tolrDocs) {
            docs.add(new SearchDocument(tolrDoc.getid()));
        }
        return new SearchResult(docs);
    }
}

SearchServiceTolrAdapter 클래스는 내부에서 TolrClient를 호출한다. 

그리고 결과값을 SearchService 에서 정의한 타입으로 변환한다.

 

어댑터 패턴의 이점

어댑터 패턴을 사용하면 클라이언트가 직접  TolrClient 와 상호작용하지 않고,

기존에 사용하던 SearchService 인터페이스만 유지하면서 새로운 검색 엔진으로 교체할 수 있다.

이렇게 변경을 최소화하면서도 새로운 기능을 도입할 수 있어 시스템 확장성에 유리하다.

어댑터 패턴을 사용하면 개방폐쇄 원칙에 맞는 코드를 구현할 수 있게되는 것이다.

 

 

어댑터 패턴의 예 : SLF4J

SLF4J(Simple Logging Facade for Java)는 어댑터 패턴을 활용하는 대표적인 프레임워크다.

SLF4J는 다양한 로깅 프레임워크(Log4j, java.util.logging 등)의 인터페이스 차이를 어댑터 패턴을 통해 통합하여,

개발자가 SLF4J 인터페이스만 사용해도 다른 로깅 프레임워크로 손쉽게 교체할 수 있도록 설계되어 있다.

이를 통해 개발자는 하나의 통합된 인터페이스로 다양한 로깅 구현체를 자유롭게 선택할 수 있게 된다.

 

상속을 이용한 어댑터 패턴

public class SearchServiceTolrAdapter extends TolrClient 
        implements SearchService {
    private TolrClient tolrClient = new TolrClient();
    
    public SearchResult search(String keyword) {
        // keyword를 tolrClient가 요구하는 형식으로 변환
        TolrQuery tolrQuery = new TolrQuery(keyword);
        // TolrClient 기능 실행
        QueryResponse response = super.query(tolrQuery);
        // TolrClient의 결과를 SearchResult로 변환
        SearchResult result = convertToResult(response);
        return result;
    }
    
    private SearchResult convertToResult(QueryResponse response) {
        List<TolrDocument> tolrDocs = response.getDocumentList().getDocuments();
        List<SearchDocument> docs = new ArrayList<SearchDocument>();
        for (TolrDocument tolrDoc : tolrDocs) {
            docs.add(new SearchDocument(tolrDoc.getid()));
        }
        return new SearchResult(docs);
    }
}

상속을 이용하여 어댑터 패턴을 구현할 수 있다. 상위클래스의 메서드를 호출하는 방식으로 코드를 작성한다.

클라이언트가 사용하는 SearchService가 인터페이스가 아닌 추상클래스라면,
자바 같이 단일 상속만을 지원하는 언어에서는 클래스 상속을 이용한 어댑터 구현에 제약이 있다.

 

상속을 사용한 어댑터 클래스는 상위 클래스의 모든 메서드를 물려받게 되므로

불필요한 메서드까지 포함될 수 있다.

반면에 구현을 사용하는 경우, 어댑터 클래스는 필요한 기능만을 명시적으로 포함할 수 있으므로

더 유연한 설계가 가능하다.

이러한 이유로 어댑터 패턴에서는 구현 방식을 더 선호하는 경향이 있다.