Adapter 패턴은 Wrapper 패턴이라고 불리기도 한다.
일반 상품을 깨끗한 포장지로 싸서 선물용 상품으로 만드는 것처럼, 무엇인가 포장해서 다른 용도로 사용할 수 있도록 변환해 주는 것이 래퍼이자 어댑터이다.
DC 12V로 동작하는 노트북을 AC 100V 전원에 연결할 때 변환을 하기위해 AC 어댑터를 사용한다.
- 제공된 것과 필요한 것 사이에 들어가서 그 사이를 채우는 것이 Adapter의 역활이다.
- ‘이미 제공된 것’과 ‘필요한 것’ 사이의 차이를 메우는 디자인 패턴을 Adapter패턴 이라고 한다.
- 제공된 코드를 그대로 사용하지 못할 때 필요한 형태로 변환이 필요할 때
2가지 Adapter 패턴
-클래스에 의한 Adapter 패턴(상속을 사용한 패턴)
-인스턴스에 의한 Adapter 패턴(위임을 사용한 패턴)
Adapter 패턴의 등장인물
Target(대상) 역
지금 필요한 메서드를 결정한다. (노트북을 작동시키는 직류 12볼트에 해당한다.)
- Print 인터페이스(상속의 경우)와 Print 추상클래스(위임의 경우가)가 이 역활을 맡았다.
Client(의뢰자) 역 - Main 클래스가 이 역활을 맡았다.
Target의 메서드를 사용해 일합니다. 직류 12 볼트로 작동하는 노트북을 말한다.
Adaptee(적응 대상자) 역 - Banner 클래스가 이 역활을 맡았다.
Adaptee는 이미 준비된 메서드를 가지는 역활이다. (교류 100볼트 AC전원)
- Adaptee의 메서드가 Target의 메서드와 일치한다면(가정에 제공되는 전압이 처음부터 12V라면 다음에 소개하는 Adapter는 등장할 필요가 없다)
Adapter(적응자) 역 - PrintBanner 클래스가 이 역활을 수행했다.
Adaptee의 메서드를 사용해서 어떻게든 Target을 만족시키는 것이 Adapter 패턴의 목적이며 임무이다.
- 클래스에 의한 Adapter 패턴일 때는 Adapter는 ‘상속’ 하여 Adaptee를 이용했다.
- 인스턴스에 의한 Adapter 패턴일 때는 ‘위임’ 하여 Adaptee를 이용한다.
클래스에 의한 Adapter 패턴(상속을 사용한 패턴)
클래스 다이어그램(상속을 사용)
Banner 클래스 -교류 100V처럼 -이미 제공된 것-
public class Banner {
private String string;
public Banner(String string){
this.string = string;
}
public void showWithParen(){
System.out.println("("+string+")");
}
public void showWithAster(){
System.out.println("*"+string+"*");
}
}
- 문자열을 괄호로 묶어서 표시하는 showWithParen 메서드
- 문자열 앞뒤에 *를 붙여서 표시하는 showWithAster메서드가 있다.
Print 인터페이스 - 직류 12V처럼 -필요한 것-
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
문자열을 괄호로 묶어 약하게 표시하는 printWeak 메서드
문자열을 *로 강조해서 표시하는 printString)이 선언되어 있다.
어댑터 역활을 담당하는 PrintBanner 클래스
public class PrintBanner extends Banner implements Print{
public PrintBanner(String string){
super(string); //부모 생성자한테 넘기기
}
@Override //Print인터페이스 오버라이드
public void printWeak() {
showWithParen(); //상속
}
@Override //Print인터페이스 오버라이드
public void printStrong() {
showWithAster(); //상속
}
}
- 제공된 Banner 클래스를 상속받아 필요한 Print 인터페이스를 구현한다.
- PrintBanner 클래스는 showWithParen 메서드로 printWeak를 구현하고 showWithAster 메서드로 printStrong을 구현한다. (어댑터 기능)
Main 클래스
어뎁터 역활하는 PrintBanner 클래스를 이용해 Hello 문자열을 약하게(괄호로) 혹은 강하게(*) 표시
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
/*
(Hello)
*Hello*
*/
PrintBanner의 인스턴스를 Print 인터페이스형 변수에 대입하는 것에 주의하자
- Print 인터페이스를 사용해서(printWeak(), printStrong() 메서드) 프로그래밍 한다.
Banner 클래스나 showWithParen 메서드, showWithAster 메서드는 Main 클래스의 코드에서 숨겼다.
- 노트북이 직류 12V로 동작하지만 어댑터 건너편에서 공급되는 전원은 교류 100V라는 걸 몰라도 되는 것과 비슷하다.
PrintBanner 클래스가 어떻게 구현됐는지 Main 클래스는 모른다. 따라서 Main 클래스를 전혀
변경하지 않고도 PrintBanner 클래스의 구현을 바꿀 수 있다.
전원의 비유와 예제 프로그램의 대응관계
전원의 비유 | 예제 프로그램 | |
제공된 것 | 교류 100V | Banner 클래스(showWithParen, showWithAster) |
변환 장치 | 어댑터 | PrintBanner 클래스 |
필요한 것 | 직류 12V | Print 인터페이스(printWeak, printString) |
인스턴스에 의한 Adapter 패턴(위임을 사용한 패턴)
위임에 대해서
위임은 "누구에게 맡긴다"는 의미이다. 중요한 회의에 참석하지 못할 때 위임장을 쓴다. 참석할 수 없으니
대신 OOO를 보내겠습니다 라고 말이다. (위임은 이양과 같다.)
Java에서 위임은 어떤 메서드의 실제 처리를 다른 인스턴스의 메서드에 맡기는 것을 말한다.
클래스 다이어그램(위임을 사용)
Main클래스와 Banner클래스는 앞의 예제와 동일하고 Print는 인터페이스가 아니라 추상 클래스라고 가정한다.
- Banner클래스를 이용하여 Print클래스와 같은 메서드를 갖는 클래스를 실현
public abstract class Print {
public abstract void printWeak();
public abstract void printStrong();
}
Java에서는 단일 상속이라서 PrintBanner 클래스를 Print와 Banner 양쪽의 하위 클래스로 정의할 수 없다.
public class PrintBanner extends Print {
private Banner banner; //banner 필드로 Banner 클래스의 인스턴스를 가진다.
public PrintBanner(String string){
// 생성자에서 필드에 있는 Banner 클래스의 인스턴스를 생성한다.
this.banner = new Banner(string);
}
@Override //추상클래스 오버라이드
public void printWeak() {
banner.showWithParen(); //banner 필드를 통해 메서드 호출
}
@Override //추상클래스 오버라이드
public void printStrong() {
banner.showWithAster(); //banner 필드를 통해 메서드 호출
}
}
전에 예제는 자신의 상위 클래스로부터 상속받은 showWithParen(), showWhitAster() 메서드를 호출했지만 이번 예제에서는 banner 필드를 경유해서 호출한다.
이처럼 PrintBanner 클래스의 PrintWeak 메서드가 호출되었을 때 자신이 처리하지 않고,
다른 인스턴스(Banner의 인스턴스)인 showWithParen() 메서드에 맡기는 것을 위임 이라고 한다.
어떤 경우에 사용하는 것일까? , 필요한 메서드가 있으면 그냥 프로그래밍하면 되지 왜 Adapter 패턴을 생각해야 할까?
프로그래밍을 하는 경우 이미 존재하는 클래스를 이용하는 경우가 흔하다.
- 해당 클래스가 충분히 테스트되어 버그가 적고 또 실제로 지금까지 사용되어 온 실적이 있다면 그 클래스를 부품으로 재사용하고 싶을 것이다.
Adapter패턴은 기존 클래스에 한겹 덧씌워 필요한 클래스를 만든다. 이패턴을 사용하면 필요한 메서드를 빠르게 만들 수 있다.
- 만약 버그가 발생하더라도 기존 클래스(Apaptee 역)에는 버그가 없는 것을 알고 있으므로 , Adapter 역의 클래스를 중심적으로 살펴보면 되고 프로그램 검사가 매우 편리해진다.
비록 소스가 없더라도
이미미 만들어진 클래스가 있고 새로운 인터페이스(API)에 맞춘다고 생각하면, Adapter 패턴을 사용하는 게 당연하게 느껴진다.
- 보통 우리는 새로운 인터페이스(API)에 맞추려고 할 때 기존 클래스의 소스를 만져서 ‘수정’ 한다.
- ‘여기를 좀 바꾸면 일이 끝나겠다’ 라고 생각하고 수정해버리면 동작 테스트가 이미 끝난 기존 클래스에 손을 댔으니 수정한 후에 다시 테스트해야 한다.
Adapter 패턴은 기존 클래스를 전혀 수정하지 않고 목적한 인터페이스(API)에 맞추려는 것이다.
- Adapter 패턴에서는 기존 클래스의 소스 프로그램이 반드시 필요한 것은 아니다.
- 기존 클래스의 사양만 알면 새로운 클래스를 만들 수 있다.
버전 업과 호환성
소프트웨어는 버전 업이 필요하다. (소프트웨어를 버전 업할 때는 ‘구버전과의 호환성’ 문제가 된다.)
- 레거시 시스템(legacy system)으로도 불리는 구버전을 버리면 소프트웨어 유지 보수는 편해지지만, 항상 그럴 수 있는 것은 아니다.
"레거시 시스템(Legacy System)"이란 표현은 기존에 사용되어온 컴퓨터 시스템, 응용 프로그램,
데이터베이스 등을 일컫는 표현입니다. 이러한 시스템들은 최신 기술에 비해 느린 처리 속도,
제한적인 기능, 보안 취약점 등의 문제가 있지만, 여전히 많은 기업이나 조직에서 사용되고 있기 때문에
그 교체가 어려운 경우가 많습니다.
- Adapter 패턴은 신버전과 구버전을 공존시키고, 유지보수까지 편하게 하도록 도와준다.
앞으로 신버전만 유지 보수하고 싶을 때는 신버전을 Adaptee 역으로 하고, 구버전을 Target 역으로 한다.
- 신버전 클래스를 이용하여 구버전의 메서드를 구현하는 Adapter 역활 클래스를 만든다.
관계를 표현한 개념도(UML 다이어그램이 아니다.)
동떨어진 클래스
Adaptee 역과 Target 역의 기능이 너무 동떨어진 경우에는 Adapter 패턴을 사용할 수 없다.
- 교류 100V 전원만으로 수돗물을 내보낼 수는 없다.
상속과 위임 어느쪽을 사용해야 할까?
상속을 사용하는 것보다 위임을 사용하는 편이 문제가 적다.
- 상위 클래스의 내부 동작을 자세히 모르면, 상속을 효과적으로 사용하기 어려운 경우가 많기 때문이다.
관련패턴
Bridge 패턴
Decorator 패턴
'책 > Java 언어로 배우는 디자인 패턴 입문' 카테고리의 다른 글
[디자인 패턴] Prototype패턴 (0) | 2023.03.12 |
---|---|
[디자인 패턴] Singleton패턴 (0) | 2023.03.11 |
[디자인 패턴] Factory Method패턴 (0) | 2023.03.11 |
[디자인 패턴] Template Method패턴 (0) | 2023.03.11 |
[디자인 패턴] Iterator패턴 (0) | 2023.02.10 |