어댑터 패턴이란 이름 그대로 클래스가 어댑터로서 사용되는 구조 패턴이다.
어댑터는 서로 호환이 되지 않는 단자를 호환시켜 작동할 수 있게끔 하는 역할을
수행한다.
이와 같이 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들을 함께
작동할 수 있도록 변환하는 역할을 수행하는 행동 패턴이라고 볼 수 있다.
예를 들어 기존 시스템에 새로운 3rd-party 라이브러리를 추가하고 싶거나, 레거시
인터페이스를 새로운 인터페이스로 교체하는 경우에 어댑터 패턴을 사용하면
코드의 재사용성을 높일 수 있다.
즉, 어댑터란 이미 구축되어 있는 것에 새로운 어떤 것을 사용할 때 양쪽 간의 호환성을
유지해 주기 위해 사용하는 것으로서, 기존 시스템에서 새로운 업체에서 제공하는 기능을
사용하려고 할 때 서로 간의 인터페이스를 어댑터를 통해 일치시켜 호환성 및 신규
기능 확장을 할 수 있다고 보면 된다.
어댑터가 레거시 인터페이스를 감싸서 새로운 인터페이스로 변환하기 때문에 Wrapper
패턴이라고도 불린다.
합성된 멤버에게 위임을 통한 어댑터 패턴
자신의 수행할 일을 인스턴스 멤버의 메서드에 전달하여 목적을 달성하는 것을
위임이라고 한다.
합성을 활용했기 때문에 런타임 중에 Adaptee
가 결정되어 유연하다.
Adaptee(Service)
: 어댑터 대상 객체, 기존 시스템 / 외부 시스템 / 써드파티 라이브러리
Target(Client interface)
: Adapter
가 구현하는 인터페이스
Adapter
: Client
와 Adaptee(Service)
중간에서 호환성이 없는 둘을
연결시켜주는 역할을 담당
- Object Adapter 방식에선 합성을 이용해 구현
- Adaptee(Service)
를 따로 인스턴스 멤버로 설정하고 위임을 통해 동작을
매치시킨다.
Client
: 기존 시스템을 어댑터를 통해 이용하려는 주체, Client Interface를
통하여 Service
를 이용할 수 있도록 된다.
클래스 상속을 이용한 어댑터 패턴
Adapter(Service)
를 상속했기 때문에 따로 객체 구현없이 바로 코드 재사용이
가능하다.
상속은 대표적으로 기존에 구현된 코드를 재사용하는 방식이지만, 자바에서는 다중
상속 불가 문제 때문에 전반적으로 권장하지 않는 방법이다.
Class Adapter 방식에선 상속을 이용해 구현한다.
Existing Class와 Adaptee(Service)
를 동시에 implements, extends
하여
구현한다.
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);
}
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
콘솔에서 입력을 받을 때 가장 많이 사용하는 클래스중 하나인 BufferedReader
는
보통 위와 같이 생성자에 InputStreamReader
를 인자로 넘겨주어 생성한다.
이 코드는 input을 행하는 System.in
을 BufferedReader
객체에 사용하고 싶은데
이 중간을 InputSteramReader
가 어댑터 역할을 수행하여 가능케 하는 것으로
해석할 수 있다.
BufferedReader
클래스를 살펴보면 생성자에 Reader
타입을 받는다.
하지만, System
클래스의 in
필드를 보면 InputStream
타입으로 구성되어 있다.
따라서 서로 타입이 맞지 않기 때문에 BufferedReader(System.in)
과 같은 형태로
사용할 수 없는 것이다. 그리하여 이 둘을 연결시켜주는 어댑터가 바로 InputStreamReader
클래스이다.
UML로 표현하여 살펴보면 아래와 같은 형태가 된다.
기존의 배열을 리스트로 변환 & 호환 작업을 해주는 Arrays.asList()
도
어댑터라 간주할 수 있다.
public static void main(String[] args) {
String[] arr = {"a", "b", "c"};
List<String> list = Arrays.asList(arr);
}