어댑터 패턴 : 한 클래스의 인터에스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다. 이 패턴을 사용하면 다른 인터페이스와의 호환성 문제를 해결할 수 있다.
여기서 말하는 어댑터는 우리가 통상 사용하는 220V 전기 플러그를 여행을 갔을때 그 나라에 맞는 규격에 해당하는 전기 플러그 변환 어댑터라고 생각하면 될 것이다.
A사 | B사 |
---|---|
A사 | B사 |
티켓선택 | 티켓선택 |
티켓출력 | 티켓선택 |
오프라인 구매 | 오프라인 구매 |
온라인 구매 | |
메뉴 가져오기 |
이런 A와 B사의 티켓판매 기계가 있다고 가정을하자. A회사는 오프라인으로만 판매를 하고, B사는 온,오프 라인 나누어서 구매를 할 수 있다. 또 메뉴를 가져오는 기능도 가지고 있다.
여기서 A사의 기술을 B사가 인수를 제안하여 시스템을 통합해서 운영하자는
계획이 있다고 가정을 하고 B사가 A사의 기존 시스템 기능을 그대로 제공하면서,
B사만 몇가지 기능을 추가하려고 할때 어떻게해야하는가?
먼저 A사의 티켓 시스템을 인터페이스로 구현하자면 다음과 같다.
TicketA.java
public interface TicketA {
void choice(int token);
void print();
void buy();
}
TicketSystemA.java
public class TicketSystemA implements TicketA{
@Override
public void choice(int token) {
System.out.println("선택된 티켓 타입은 " + token + "입니다.");
}
@Override
public void print() {
System.out.println("티켓을 출력합니다.");
}
@Override
public void buy() {
System.out.println("티켓을 구매합니다.");
}
}
다음은 B회사의 티켓 시스템이다.
TicketB.java
public interface TicketB {
void choice(int token);
void print();
void buyOnOffline();
void buyOnOnline();
String getMenu();
}
TicketSystemB.java
public class TicketSystemB implements TicketB{
@Override
public void choice(int token) {
System.out.println("선택된 티켓 타입은 " + token + "입니다.");
}
@Override
public void print() {
System.out.println("티켓을 출력합니다.");
}
@Override
public void buyOnOffline() {
System.out.println("오프라인으로 구매합니다.");
}
@Override
public void buyOnOnline() {
System.out.println("온라인으로 구매합니다.");
}
@Override
public String getMenu() {
return "메뉴정보를 가져왔습니다.";
}
}
이 4개의 클래스와 인터페이스들로 시스템을 구현해본다.
TicketMachine.java
public class TicketMachine {
public static void main(String[] args) {
TicketA ticketA = new TicketSystemA();
ticketA.choice(1);
ticketA.buy();
ticketA.print();
System.out.println("----------------------");
TicketB ticketB = new TicketSystemB();
ticketB.choice(1);
ticketB.buyOnOffline();
ticketB.buyOnOnline();
ticketB.print();
System.out.println(ticketB.getMenu());
}
}
이렇게 실행하여 둘다 잘 작동하는 것을 확인한다.
이제 아까 조건을 생각해보자.
TicketB ticketB = new TicketSystemA();
기존 B사의 시스템안에 A사의 시스템이 정상적으로 돌아가야 하기 때문에, 이것을 해결하기 위해서는 A사의 인터페이스를 G사에 맞게 다시 정의해야 한다. 하지만, 이것은 소스의 중복이 생기게 된다.
public class NewTicketSystem implements TicketB{
@Override
public void choice(int token) {
System.out.println("선택된 티켓 타입은... " + token + " 입니다");
}
@Override
public void print() {
System.out.println("티켓을 출력합니다..");
}
@Override
public void buyOnOffline() {
System.out.println("티켓을 구매합니다..");
}
@Override
public void buyOnOnline() {
throw new UnsupportedOperationException("지원되지 않는 기능");
}
@Override
public String getMenu() {
throw new UnsupportedOperationException("지원되지 않는 기능");
}
}
B사의 인터페이스로 정의한 A사의 시스템이다. 상단부터 차례로 choice, print, buyOnOffline 메소드를 정의하기 위해서 기존의 코드를 중복한 후 사용하였다.
만약 메소드가 지금처럼 5개가 전부가 아닌 하나의 거대한 시스템이었다면 엄청난 중복과 비효율을 발생하게 될 것이다.
이럴때 사용하는 패턴이 바로 어댑터 패턴 이다❗❗
TicketAdapter.java
public class TicketAdapter implements TicketB{
private TicketA ticket;
public TicketAdapter(TicketA ticket){
super();
this.ticket = ticket;
}
@Override
public void choice(int token) {
ticket.choice(token);
}
@Override
public void print() {
ticket.print();
}
@Override
public void buyOnOffline() {
ticket.buy();
}
@Override
public void buyOnOnline() {
throw new UnsupportedOperationException("지원되지 않는 기능");
}
@Override
public String getMenu() {
throw new UnsupportedOperationException("지원되지 않는 기능");
}
}
이런식으로 TicketB 인터페이스를 implements하여 B사의 메소드들을 전부 Override 시킨 다음
생성자로 A사의 TicketSystemA를 받고, 원래의 시스템을 그대로 유지하려는 부분에 A사의 메소드를 호출한다. 그리고 지원하지 않는 기능은 예외처리를 하여 정의하였다.
TicketMachine.java
public class TicketMachine {
public static void main(String[] args) {
TicketB ticketB = new TicketAdapter(new TicketSystemA());
ticketB.choice(1);
ticketB.buyOnOffline();
ticketB.print();
try{
System.out.println(ticketB.getMenu());
}catch (UnsupportedOperationException e){
System.out.println("이 서비스는 다른 시스템에서 제공되는 기능입니다.");
}
}
}
ticketB를 TicketSystemA의 인스턴스로 생성하여 실행을 했으니 ticketB의 getMenu는 예외로 처리되어 catch부분이 실행되서 "이 서비스는 다른 시스템에서 제공되는 기능입니다." 라는 로그가 콘솔에 찍히게 될 것이다.