어댑터 패턴은 기존에 생성된 클래스를 재사용할 수 있도록 중간에서 맞춰주는 역할을 하는 인터페이스를 만드는 패턴으로, 인터페이스가 호환되지 않는 클래스들을 함께 이용할 수 있도록 타 클래스의 인터페이스를 기존 인터페이스에 덧씌우는 형태로 구현한다.
다중 상속을 이용하는 클래스 어댑터와 위임을 이용하는 객체 어댑터의 두 가지 형태로 사용된다.
클래스 어댑터: 상속을 사용하며, 어댑터는 동시에 두 객체의 인터페이스를 상속한다. 이 방식은 C++ 와 같이 다중 상속을 지원하는 프로그래밍 언어에서만 구현할 수 있다. 다중상속을 지원하지 않는 자바에서는 사용할 수 없다
객체 어댑터: 객체 합성 원칙을 사용한다. 어댑터는 한 객체의 인터페이스를 구현하고 다른 객체는 래핑한다. 위 합성은 모든 인기 있는 프로그래밍 언어로 구현할 수 있다.
해당 예시 코드는 Java로 작성되어 클래스 어댑터 패턴을 사용할 수 없어 객체 어댑터 패턴으로 작성되었다.
// IPhoneCharger.java (Target)
public interface IPhoneCharger {
String chargeIPhone();
}
// GalaxyCharger.java (Adaptee)
public class GalaxyCharger {
public String ChargeGalaxy() {
return "Charging ...";
}
}
위의 예시에서 아이폰 충전기와 갤럭시 충전기 두 종류의 충전기가 있다. 갤럭시 충전기는 아이폰 충전기 인터페이스를 상속받지 않고 있다. (호환이 되지 않는다)
이럴 경우 어댑터 클래스를 만들어 아이폰 충전기 인터페이스를 상속받고 갤럭시 충전기의 충전기능을 아이폰 충전기의 충전기능과 연결해준다.
// Adapter.java (Adapter)
public class Adapter implements IPhoneCharger {
private final GalaxyCharger galaxyCharger;
public Adapter(GalaxyCharger galaxyCharger) {
this.galaxyCharger = galaxyCharger;
}
@Override
public String chargeIPhone() {
return galaxyCharger.ChargeGalaxy();
}
}
위와 같이 IPhoneCharger의 chargeIphone 기능이 호출되면 GalaxyCharger의 chargeGalaxy 기능을 호출해 직접 GalaxyCharger를 호출하지 않고도 chargeGalaxy 기능을 사용할 수 있게 된다.
// Client.java
public class Client {
public static void main(String[] args) {
GalaxyCharger galaxyCharger = new GalaxyCharger();
IPhoneCharger iPhoneCharger = new Adapter(galaxyCharger);
System.out.println(iPhoneCharger.chargeIPhone());
}
}
// 출력 결과
Charging ...
앞선 예제에서 살펴봤듯이 어댑터 패턴을 이용하면 기존의 클래스를 수정하지 않고도 클라이언트에서 새로운 인터페이스를 사용할 수 있다. 이는 기존의 코드를 재사용하고 코드 중복을 줄여주는 데 도움이 된다. 또한 클래스 간의 결합도를 줄여주어, 소스 코드 변경이 필요할 때 쉽게 수정할 수 있다는 장점도 있다.
그러나 어댑터 패턴을 사용하면 어댑터 클래스를 추가로 작성해야 하기 때문에 소스 코드가 늘어나게 된다. 이는 코드의 복잡성을 증가시키고, 유지 보수를 어렵게 만들 수도 있다. 또한 어댑터가 중간에 데이터를 변환하는 과정에서 추가적인 처리 시간과 오버 헤드가 발생할 수도 있다.
앞서 언급한 이유로 어댑터 패턴의 무분별한 사용은 권장하지 않는다. 따라서 호환되지 않는 인터페이스를 가진 클래스들이 함께 작동해야 하거나, 이미 존재하는 클래스의 인터페이스가 요구 사항과 맞지 않거나 또는 기존 클래스에 원하는 인터페이스가 없는 경우 어댑터 패턴을 고려하는 것이 좋다.
예를 들어, 서드파티 라이브러리나 API를 사용하는데 그 인터페이스가 애플리케이션 코드와 잘 맞지 않는 경우, 어댑터 패턴을 사용해 서드파티 라이브러리 및 API 내부 구현에 영향을 받지 않으면서, 프로젝트에 필요한 인터페이스를 생성할 수 있다.
[디자인패턴] 디자인패턴이란? - 생성패턴, 구조패턴, 행위패턴