객체지향 어댑터도 결국 일상 생활에서 쓰이는 어댑터와 똑같은 역할을 한다. 어떤 인터페이스를 클라이언트에서 요구하는 형태의 인터페이스에 적응시켜주는 역할을 한다.
어댑터는 클라이언트로부터 요청을 받아서 새로운 업체에서 제공하는 클래스(vendor class)에서 받아들일 수 있는 형태의 요청으로 변환시켜주는 중개인 역할을 한다.
기존 시스템과 업체에서 제공한 클래스의 코드에는 변화가 없고, 새 코드에 해당하는 어댑터만 추가하면 된다.
public interface Duck {
public void quack();
public void fly();
}
public interface Turkey {
public void gobble(); // 칠면조는 꽥꽥거리지 않고 골골거린다.
public void fly(); // 칠면조도 날 순 있지만, 별로 멀리 날 수 없다.
}
public class MallardDuck implements Duck{
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
System.out.println("I'm flying");
}
}
public class WildTurkey implements Turkey{
@Override
public void gobble() {
System.out.println("gobble gobble");
}
@Override
public void fly() {
System.out.println("I'm flying a short distance");
}
}
어탭터 클래스
// Duck 객체가 모자라서 Turkey 객체를 대신 사용해야 하는 상황.
// 인터페이스가 다르기에 어댑터 클래스를 만들어야 함.
public class TurkeyAdapter implements Duck{
// 원래 형식의 객체에 대한 레퍼런스가 필요.
// 생성자에서 레퍼런스를 받아오는 작업을 함.
Turkey turkey;
public TurkeyAdapter(Turkey turkey){
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
// 두 인터페이스 모두 fly()가 들어있지만 칠면조는 오리처럼 멀리 날지 못하다.
// Turkey의 fly() 메서드를 Duck의 fly() 메서드에 대응시키기 위해 5번 호출하여 더 멀리 날아가도록 함.
@Override
public void fly() {
for(int i=0; i<5; i++){
turkey.fly();
}
}
}
Main
public class MainTest {
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
// 어댑터 적용
// 클라이언트에서 요구하는 인터페이스는 오직 Duck 형식이다.
// 이 Duck 형식의 인터페이스에 Turkey가 맞추기 위해 어뎁터를 사용한다.
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("turkey says..");
turkey.gobble();
turkey.fly();
System.out.println();
System.out.println("TurkeyAdapter says..");
turkeyAdapter.quack();
turkeyAdapter.fly();
}
}
어댑터 패턴: 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환. 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있다.
어댑터 패턴에는 여러 객체지향 원칙이 반영됨. 어댑티를 새로 바뀐 인터페이스로 감쌀 때는 '객체 구성'을 사용. 이런 접근법을 쓰면 어댑티의 어떤 서브클래스에 대해서도 어댑터를 쓸 수 있다는 장점이 있다.
그리고 이 패턴에서느 클라이언트를 특정 구현이 아닌 인터페이스에 연결시킨다. 각각 서로 다른 백엔드 클래스들로 변환시키는 여러 어댑터를 사용할수도 있다. 이렇게 인터페이스를 기준으로 코딩을 했기에 타겟 인터페이스만 제대로 지킨다면 나중에 다른 구현을 추가하는 것도 가능하다.
추가 학습 사항으로 (1) 클래스 어댑터(다중 상속 가능할 때)가 있다. 앞서 학습한 내용들은 객체 어댑터 방식에 속한다.
(2) p286 어댑터 실전 예제