[헤드퍼스트 디자인패턴] 7-1. 어댑터 패턴
어댑터 패턴
호환되지 않는 모양의 인터페이스를 사용하고 싶을 때에는 어댑터 패턴의 어댑터를 이용해 클라이언트가 요구하는 모양의 다른 인터페이스로 변환하여 사용할 수 있습니다. 어댑터는 인터페이스가 호환되지 않아 함께 쓸 수 없었던 클래스를 사용할 수 있도록 도와줍니다. 클라이언트는 인터페이스를 사용하면서도 중간에 어댑터가 있다는 사실을 알지 못합니다.
구조
1. Client: 서비스 인터페이스를 사용하는 고객입니다.
2. Client Interface: Client가 사용하는 서비스들의 공통 로직(틀)을 의미합니다.
3. Adapter: Service를 Client가 사용할 수 있는 Client Interface 형식으로 변환합니다.
4. Service: Client가 사용하고자 하는 타사의 호환되지 않는 서비스입니다.
예제
Turkey 클래스를 Duck 인터페이스처럼 사용할 수 있도록 도와주는 TurkeyAdapter를 만들어봅시다.
public interface Duck {
public void quack();
public void fly();
}
public interface Turkey {
public void gobble();
public void fly();
}
public class WileTurkey implements Turkey {
public void gobble() {
System.out.println("골골");
}
public void fly() {
System.out.println("짧은 거리를 날고 있어요!");
}
}
현재 Duck과 Turkey는 서로 다른 인터페이스로 구현되어 있기 때문에 Turkey 인스턴스를 Duck 인터페이스처럼 사용하지 못합니다. Duck을 상속하는 TurkeyAdapter 클래스를 만들어, Turkey를 Duck 인스턴스처럼 사용할 수 있도록 변환해보겠습니다.
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
public void quack() {
turkey.gobble();
}
public void fly() {
// 칠면조가 오리처럼 멀리 날기 위해 fly 메소드를 5번 실행
for(int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
public class DuckTestDrive {
public static void main(String[] args) {
Turkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
이제 TurkeyAdapter를 사용하면 칠면조도 오리처럼 꽥꽥거리고, 날 수 있습니다..... 😎
위와 같은 어댑터 패턴 구현 방법을 객체 어댑터라고 부릅니다.
객체 어댑터
객체 어댑터의 장점
- 구성을 사용하기 때문에 어댑티 클래스와 그 어댑티의 서브클래스에 대해서도 어댑터 역할을 할 수 있습니다.
- 코드량이 줄어들고 유연성이 증가합니다.
- 어댑터에 행동을 추가하면 어댑티 클래스와 모든 서브 클래스에 새 행동이 적용됩니다.
클래스 어댑터
구성을 사용하는 객체 어댑터와 달리 클래스 어댑터는 타깃과 어댑티를 모두 서브클래스로 만들어 사용합니다.
클래스 어댑터 패턴은 다중 상속이 필요한데, 자바에서는 다중 상속이 불가능해서 구현을 할 수 없다고 하네요.
클래스 어댑터의 장점
- 특정 어댑티 클래스에만 어댑터를 적용할 수 있습니다.
- 어댑티 전체를 다시 구현하지 않아도 됩니다.
- 서브클래스이기 때문에 어댑티의 행동을 오버라이드 할 수 있습니다.