Head First Design Patterns 책을 읽으며 정리한 내용입니다. 문제가 될 시 글을 내리도록 하겠습니다!
![](https://blog.kakaocdn.net/dn/bbVc71/btrqBe6hwMq/Iv5cd7LnOfbe4gWKssUhh1/img.jpg)
Adapter Pattern 이란?
Adapter Pattern - 클래스의 인터페이스를 클라이언트에서 요구하는 다른 인터페이스로 변환한다.
인터페이스가 호환되지 않아 사용하지 못하던 클래스들을 사용할 수 있게 해 줍니다.
어떤 프로그램이 있는데, 새로운 업체에서 제공하는 기능을 사용할려고 한다 해봅시다.
하지만 기존 프로그램의 인터페이스와 새로 제공된 클래스의 인터페이스가 일치하지 않아 사용에 어려움이 있습니다.
이럴때 필요한 것 이 Adapter 입니다!
어댑터를 사용하면 기존시스템에서의 요청이 어댑터를 통해 변환되어 새로 업체에서 제공한 클래스에서 요청을 받아들일 수 있도록 변환시켜 줍니다!
![](https://blog.kakaocdn.net/dn/bjdRb5/btrqBeZwWOQ/zpkdkiLG74J2h3iiSLLBu1/img.png)
패턴 소개
우선 오리와 칠면조 클래스가 있다고 해봅시다. 다음과 같이 말이죠!
오리먼저 살펴봅시다! 매우 간단한 기능만을 수행하고 있습니다.
public interface Duck {
public void quack();
public void fly();
}
public class MallardDuck implements Duck {
public void quack(){
System.out.println("Quack");
}
public void fly(){
System.out.println("I'm flying");
}
}
다음으로는 칠면조를 살펴봅시다!
public interface Turkey {
public void gobble();
public void fly();
}
public class WildTurkey implements Turkey {
public void gobble(){
System.out.println("Gobble gobble");
}
public void fly(){
System.out.println("I'm flying a short distance");
}
}
문제는 Duck 객체의 수가 모자라 임시방편으로 Turkey객체를 대용해야하는 상황이라 해봅시다!
이때 Duck과 Turkey의 인터페이스가 달라 바로 사용할수는 없습니다.
Turkey에 어댑터를 적용시켜 Duck 처럼 사용할수 있도록 해야합니다! 어댑터 클래스는 다음과 같을 것 입니다.
public class TurkeyAdapter implements Duck{ // Duck의 인터페이스를 모두 구현해야합니다.
Turkey turkey;
public TurkeyAdapter(Turkey turkey){
this.turkey = turkey;
}
public void quack(){
turkey.gobble();
}
public void fly(){
for(int i = 0; i < 3; i++){
turkey.fly();
}
}
}
이제 정상적으로 작동하는지 확인해봐야 겠죠?
테스트 코드 먼저 살펴봅시다!
public class Client {
public static void main(String[] args){
MallardDuck duck = new MallardDuck(); // Duck 생성
WildTurkey turkey = new WildTurkey(); // Turkey 생성
Duck turkeyToDuck = new TurkeyAdapter(turkey); // Turkey에 어댑터 적용!!
System.out.println("칠면조가 말하길...");
turkey.gobble();
turkey.fly();
System.out.println("\n오리가 말하길...");
testDuck(duck);
System.out.println("\n터키어댑터가 말하길...");
testDuck(turkeyToDuck); // !! 오리대신 칠면조를 인자로 넘김 !!
}
static void testDuck(Duck duck){
duck.quack();
duck.fly();
}
}
위 코드의 실행 결과는 다음과 같습니다.
![](https://blog.kakaocdn.net/dn/bcGHE2/btrqwuJxscQ/eedk30MokANcTMkj4CbkQ0/img.png)
대략적인 감이 생겼을태니 전체적인 숲을 살펴봅시다~~
전체적인 사용에서의 흐름은 다음과 같습니다.
![](https://blog.kakaocdn.net/dn/DRlZ1/btrqBdTQZkS/OV724M7uQf5lTM9tqb8XNk/img.png)
클라이언트 -> request() -> 어댑터 - translatedRequest() -> 어댑티
클라이언트는 타겟 인터페이스에 맞게 구현되며, 어댑터는 타겟 인터페이스를 구현하며, 어댑티 인스턴스가 들어있음.
(ps 어댑티 란 어댑터를 가운데 두고 클라이언트와 정 반대 위치하는 것을 부르는 명칭 입니다)
위의 코드에서도 보면 Client class에서 Duck타입의 turkeyToDuck 참조변수가 존재합니다.
우리가 사용할 대상은 Duck타입 이여햐 한다는 의미입니다! 문제는 Duck의 수가 모자라 칠면조를 사용해야한다는 점 이죠!
어댑터(TurkeyAdapter)에 칠면조 인스턴스(어댑티 인스턴스) 를 넘겨주면 칠면조 인터페이스(어댑티 인터페이스) 에서 Duck타입의 인터페이스(타겟 인터페이스)를 구현한 모양으로 변신할수가 있었습니다.
클라이언트에서 어댑터를 사용하는 방법
1) 클라이언트에서 타겟 인터페이스를 사용하여 메소드를 호출함으로써 어댑터에 요청합니다.
2) 어댑터에서는 어댑티 인터페이스를 사용하여 그 요청을 어댑티에 대한 메소드 호출로 변환합니다.
어댑터에서 칠면조의 함수들을 호출하는 것
3) 클라이언트에서는 호출 결과를 받긴 하지만 어댑터가 있는지 전혀 알지 못합니다.
클라이언트는 그냥 "오리처럼" 보이는 놈에게 울어보라 요청했을 뿐입니다. 그 오리처럼 보이는 놈은 사실 오리가 아닌거죠!
오리의 탈을 쓴 칠면조 였군요 ㅎㅎ. 울어보라는 요청에 대해 사실은 칠면조가 운 것이라 이말입니다!
하지만 사용자는 중간의 어댑터가 변환했는지 알수 없습니다! 계획이 성공한거죠!
이제 다이어그램으로 확인해 봅시다! 위에서 만들었던 코드를 떠올리며 살펴봅시다!
![](https://blog.kakaocdn.net/dn/bbZwmT/btrquOuTX2T/HDbP1alFkIq7ncmRnKXYpK/img.png)
어댑티를 새로 바뀐 인터페이스로 감쌀때는 객체 구성(Composition)을 사용합니다.
이런 방식을 사용하면 스트래티지 패턴에서처럼 어댑티의 어떤 서브클래스도 어댑터로 감쌀 수 있게됩니다!
어댑터에는 두종류가 있습니다!
1) 객체 어댑터 (지금까지 위에서 언급한 방식)
2) 클래스 어댑터 (이 방식은 다중 상속이 필요함. 그러나 자바는 다중상속이 불가능함)
![](https://blog.kakaocdn.net/dn/cjSBYy/btrqAeeDoKa/RkRbLzeQsDsWGuF7gT2fu0/img.gif)
어댑터를 만들때 어댑티와 타겟 모두의 서브클래스로 만들고있습니다. 구성이 아니라는 점 명심하세요!
좀더 실전적인 문제를 풀어봅시다!
Iterator를 사용해본 경험들 있으시죠?? 요즘이야 Iterator를 주로 사용하지만, 레거시 코드들을 보면 가끔 Enumeration이 튀어나오기도 합니다. 이 둘간에 어댑터를 만들어주면 좀더 편하지 않을까요? 우선 다음 정의를 살펴보시죠~~
Enumeration ( hasMoreElements(), nextElement() )
Enumeration을 리턴하는 elements() 메소드가 구현되어 있었던, 초기 컬렉션 형식(Vector, Stack, Hashtable 등)
Enumeration 인터페이스를 이용하면 컬렉션 내에서 각 항목이 관리되는 방식에는 신경 쓸 필요 없이 컬렉션의 모든 항목에 접근이 가능하다.
Iterator ( hasNext(), next(), remove() )
썬에서 새로운 컬렉션 클래스를 출시하면서, Enumeration과 마찬가지로 컬렉션에 있는 일련의 항목들에 접근할 수 있게 해 주면서 항목을 제거할 수 도 있게 해 주는 iterator라는 인터페이스를 이용하기 시작했다.
타겟 인터페이스는 당연히 최근 사용하는 Iterator의 인터페이스가 될 것 입니다.
따라서 어댑티는 Enumeration이 되겠군요
Iterator의 hasNext()는 Enumeration의 hasMoreElements()가 담당하고,
Iterator의 Next()는 Enumeration의 nextElement()가 담당하면 손쉽습니다.
문제는 마지막 남은 Iterator의 remove()를 어떻게 해결할 것 인가? 입니다.
어댑터 차원에서 완벽하게 작동하는 remove()를 구현할수는 없습니다. 그나마 가장 좋은 차선채은 예외를 던지는 것 입니다.
이때를 위해 UnsupportedOperationException을 지원하도록 만들었습니다.
이런경우는 어댑터가 완벽하게 적용될 수 없는 경우입니다. 따라서 클라이언트 쪽 에서는 예외가 발생할 수 있음을 인지해두고 작업을 수행해야 합니다.
public class EnumerationIterator implements Iterator{
Enumeration enumeration;
public EnumerationIterator(Enumeration enumeration) {
this.enumeration= enumeration;
}
@Override
public boolean hasNext(){
return enumeration.hasMoreElements();
}
@Override
public Object next() {
return enumeration.nextElement();
}
@Override
public void remove() {
throw new UnsupportedOperationException(); // 예외 UnsupportedOperationException
}
}
'BackEnd > Design Patterens' 카테고리의 다른 글
[Design Patterns] Template Method Pattern : 템플릿 메소드 패턴 (0) | 2022.01.13 |
---|---|
[Design Patterns] Facade Pattern : 퍼사드 패턴 (0) | 2022.01.13 |
[Design Patterns] Command Pattern : 커맨드 패턴 (0) | 2022.01.13 |
[Design Patterns] Singleton Pattern : 싱글턴 패턴 (0) | 2022.01.13 |
[Design Patterns] Factory Pattern : 팩토리 패턴 (0) | 2022.01.13 |
댓글