디자인 패턴(구)/구조 패턴

어탭터(Adaptor) 패턴이란?

공대키메라 2022. 4. 7. 12:10

1. 어탭터 패턴이란?

의도

클래스의 인터페이스를 사용자가 기대하는 인터페이스 형태로 적응시킨다. 서로 일치하지 않는 인터페이스를 갖는 클래스들을 함께 동작시킨다. 

 

즉, 기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴

사용 시기

  • 기존 클래스를 사용하고 싶은데 인터페이스가 맞지 않을 때
  • 이미 만든 것을 재사용하고자 하나 이 재사용 가능한 라이브러리를 수정할 수 없을 때
  • 이미 존재하는 여러 서브클래스를 사용해야 하는데 서브클래스들의 상속으로 인터페이스를 다 개조하는것이 불가능할 때

구조

  • Target : 사용자가 사용할 응용 분야에 종속적인 인터페이스를 정의하는 클래스
  • Client : Target 인터페이스를 만족하는 객체와 동작할 대상
  • Adapdee : 인터페이스의 적응이 필요한 기존 인터페이스를 정의하는 클래스
  • Adaptor : Target 인터페이스에 Adaptee의 인터페이스를 적응시키는 클래스

 

장점

  • 기존 코드를 변경하기 않고 원하는 인터페이스 구현체를 만들어 재사용 가능
  • 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.

단점

  • 새 클래스가 생겨 복잡도가 증가할 수 있다. 경우에 따라서는 기존 코드가 해당 인터페이스를 구현하도록 수정하는 것이 좋은 선택일 수 있다. 

2. 구현

이번 정리글은 약간 너무 편하게 느껴지는데

 

사실 adaptor라고 하면 무언가를 변환해주는 것이라고 생각하면 쉽고, 그러한 기능을 사실 생각해보면 mapping해준다던지, dto를 사용한다던지, 간단한 것이 전부 여기서 속할 수 있다고 생각한다. 

 

필자의 정보를 담고 있는 절대값 개념과 비슷한 저장소가 있다고 하자. 

 

이 정보는 각국에서 원하는 양식에 맞춰서 변환이 되어야한다. 

 

물론 이 정보를 받는 변수 이름이던지, 그러한 전반적인 것이 잘 변환되어야 한다. 

 

그래서 필자는 만국 공통 개인 정보를 담는 IDInfoHandler를 정의하였다.

 

IDInfo.java

package me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code.mydata;

public interface IDInfo {

    String getName();

    String getCountry();

}

IdInfoService.java

package me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code.mydata;

public interface IDInfoService {

    IDInfo loadIDInfo(String name);
}

IdInfoHandler.java

package me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code.mydata;

public class IDInfoHandler {

    IDInfoService idInfoService;

    public IDInfoHandler(IDInfoService idInfoService) {
        this.idInfoService = idInfoService;
    }

    public String findUser(String name, String country){
        IDInfo idInfo = idInfoService.loadIDInfo(name);
        if (idInfo.getCountry().equals(country)){
            return idInfo.getName();
        } else{
            throw new IllegalArgumentException();
        }
    }
}

 

이 모든 국가가 범용적으로 등록한 정보는 각국에서 차이점이 존재한다.

그래서 IDInfoHandler를 통해서 KoreaId, 즉 한국의 규격에 맞는 정보로 받을 것이다. 

 

규격이 다르다고 하나, 기본적으로 필요로 하는 공통적인 데이터라고 말하면 그건... 이름이나 국적이 아닐까 한다. 

 

필자는 이 IdInfo를 KoreaId의 규격에 맞게 IDInfoHandler를 통해서 정리할 것이다. 

koreaID.java

package me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code;

public class KoreaId {

    private String koreanName;

    private String country;

    private String address;

	//getter & setter
}

KoreaIdService.java

package me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code;

public class KoreaIdService {

    public KoreaId findKoreaIdByName(String name){
        KoreaId koreaId = new KoreaId();
        koreaId.setKoreanName(name);
        koreaId.setCountry(name);
        koreaId.setAddress(name);
        return koreaId;
    }

    public void updateKoreaId(KoreaId koreaId){}
    public void makeKoreaId(KoreaId koreaId){}

}

KoreaIdAndIDInfo.java

package me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code;

import me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code.mydata.IDInfo;

public class KoreaIdAndIDInfo implements IDInfo {

    private KoreaId koreaId;

    public KoreaIdAndIDInfo(KoreaId koreaId) {
        this.koreaId = koreaId;
    }

    @Override
    public String getName() {
        return koreaId.getKoreanName();
    }

    @Override
    public String getCountry() {
        return koreaId.getCountry();
    }
}

KoreaIdAndIdInfoService.java

package me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code;

import me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code.mydata.IDInfo;
import me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code.mydata.IDInfoService;

public class KoreaIdAndIdInfoService implements IDInfoService {

    KoreaIdService koreaIdService;

    public KoreaIdAndIdInfoService(KoreaIdService koreaIdService) {
        this.koreaIdService = koreaIdService;
    }

    @Override
    public IDInfo loadIDInfo(String name) {
        return new KoreaIdAndIDInfo(koreaIdService.findKoreaIdByName(name));
    }
}

ImmigrationCheckPoint.java

package me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code;

import me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code.mydata.IDInfoHandler;
import me.whiteship.designpatterns._02_structural_patterns._06_adapter._my_code.mydata.IDInfoService;

public class ImmigrationCheckpoint {

    public static void main(String[] args) {
        KoreaIdService koreaIdService = new KoreaIdService();
        IDInfoService idInfoService = new KoreaIdAndIdInfoService(koreaIdService);
        IDInfoHandler idInfoHandler = new IDInfoHandler(idInfoService);
        String user = idInfoHandler.findUser("bangal", "bangal");
        System.out.println("user = " + user);
    }

}

 

각각 필요한 정보 검색 및 서비스를 실행해주는,

다시 말해서 우리가 정해진 틀을 Korea 스타일에 맞추도록 하는 것을 입국 심사대(Immigration Checkpoint)에서 만들어내려고 한다. 

 

KoreaIdAndIdInfoService에서 한국용 Id 를 주입해서 받으려 하는데 이 범용 정보로 변환해서 받고싶은 것이다. 

 

그래서 IDInfoHandler에 KoreaService를 받아서 최소한의 정보가 맞는지 비교작업을 해준다.

 

KoreaId 클래스는 예제상 편리한 설명을 위해 이름과 모든 정보를 동일하게 세팅했다.

 

그래서 우리가 입력한 값이 해당 값이 일정 조건을 만족한다면 그 KoreaId의 값을 IDInfo에서 검색해서

 

이에 해당하는 이름을 반환해준다.

 


여기서 이제 의도와 구조를 다시 살펴보자. 

 

의도

클래스의 인터페이스를 사용자가 기대하는 인터페이스 형태로 적응시킨다. 서로 일치하지 않는 인터페이스를 갖는 클래스들을 함께 동작시킨다.

 

즉, 기존 코드를 클라이언트가 사용하는 인터페이스의 구현체로 바꿔주는 패턴

 

구조

  • Target : 사용자가 사용할 응용 분야에 종속적인 인터페이스를 정의하는 클래스
  • Client : Target 인터페이스를 만족하는 객체와 동작할 대상
  • Adapdee : 인터페이스의 적응이 필요한 기존 인터페이스를 정의하는 클래스
  • Adaptor : Target 인터페이스에 Adaptee의 인터페이스를 적응시키는 클래스

 

Target에 해당하는 것은 IdInfo, IDInfoService인 것 같다. 사용자가 사용할 응용 분야에 종속적인 인터페이스가 Target이다.

 

이것을 우리가 구체적으로 사용하는 실제 나라를 생각해보면, 현재 Korea이다

 

Adapdee는 인터페이스의 적응이 필요한 기존 인터페이스를 정의한다고 한다.

그러면 KoreaIdAndIDInfo 는 IDInfo에, KoreaIDAndIDInfoService는 IDInfoService의 기존 인터페이스를 정의한다. 

그러므로 위의 두개가 Adaptee라고 생각된다. 

 

그리고 이것을 우리의 범용 IDInfo로 변환 역할을 해주는 것은 IDInfoHandler이다. 

 

결국, 우리가 의도한 인터페이스로 정의한 것은 KoreaIDAndIDInfo, KOreaIDAndIDInfoService인데 서로 일치하지 않는 인터페이스를 가진 클래스들을 Handler를 통해 작동시킬 수 있었다. 

 


어댑터라는 개념은 콘센트 어댑터를 생각하면 아주 쉽게 연상되지만 이것을 코드로 분리해서 구현하기 생각보다 어려웠다. 다시 장점과 단점을 보겠다.

 

장점

  • 기존 코드를 변경하기 않고 원하는 인터페이스 구현체를 만들어 재사용 가능
  • 기존 코드가 하던 일과 특정 인터페이스 구현체로 변환하는 작업을 각기 다른 클래스로 분리하여 관리할 수 있다.

단점

  • 새 클래스가 생겨 복잡도가 증가할 수 있다. 경우에 따라서는 기존 코드가 해당 인터페이스를 구현하도록 수정하는 것이 좋은 선택일 수 있다. 

기존 코드인 mydata에 담긴 클래스들은 우리가 수정하지 않았다. 그렇지만, 클래스가 한번 작업하는데 4개나 생겨버린것을 확인할 수 있었고, 덕분에 복잡하고 굉장히 헷갈린다

 

구조패턴(structural patterns)은 클래스나 객체를 조합해 더 큰 구조를 만드는 패턴이다.

 

이렇게 기존의 클래스를 조합해서 더 큰 구조로 확장해가는 것을 확인했다.

 

이렇게 어댑터 패턴을 보게 되었는데 이상하거나 수정했으면 좋겠는 부분은 언제든지 피드백으로 주면 감사하겠다.