[디자인패턴] 어뎁터 패턴 (Adapter Pattern)
어뎁터 패턴 (Adapter Pattern) : 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환한다. 이 패턴을 사용하면 다른 인터페이스와의 호환성 문제를 해결할 수 있다.
여기서 말하는 어뎁터(Adapter)를 설명하자면..
보통 직구를 해서 전자제품을 구매하거나, 해외여행을 갈 때 한국과는 다른 규격의 전기 플러그를 사용할 때가 있습니다. 그럴때 보통 '돼지코'라고 불리는 플러그 변환 어뎁터를 씁니다.
이 어뎁터가 특정 규격의 플러그를 다른 플러그와 호환이 가능하게 하듯이, 객체 지향 프로그래밍에서도 비슷하게 사용할 수 있습니다.
A시스템사는 몇년 전 부터 식권 발매 시스템을 개발하여 운영하고 있습니다.
이 시스템이 안정적으로 운영되면서 최근 같은 사업을 하던 G그룹이 인수를 제안하였고, 시스템을 통합하여 운영할 계획을 세웠습니다.
G사는 A사의 기존의 시스템의 기능을 그대로 제공하면서, 몇 가지 기능을 좀 더 추가하려고 합니다.
이럴때는 어떻게 해야할까요?
A사와 G사의 식권 발권 시스템은 이런 기능들을 가지고 있습니다.
A사 |
G사 |
식권 선택 |
식권 선택 |
식권 출력 |
식권 출력 |
구매 (오프라인) |
오프라인 구매 |
|
온라인 구매 |
|
메뉴 들고오기 |
A사와 G사는 식권 선택과 출력의 기능은 동일하나, A사는 오프라인만 판매를하고 G사는 오프라인과 온라인을 나누어서 구매할 수 있도록 나누어져 있습니다. 또한 메뉴정보를 가져오는 기능도 가지고 있습니다.
A사의 식권 시스템을 인터페이스로 구현합니다.
1 2 3 4 5 6 7 | public interface TicketA { public void choice(int token); public void print(); public void buy(); } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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("식권을 구매합니다.."); } } | cs |
G사의 식권 시스템을 인터페이스로 구현합니다.
1 2 3 4 5 6 7 8 9 10 11 | public interface TicketG { public void choice(int token); public void print(); public void buyOnOffline(); public void buyOnOnline(); public String getMenu(); } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class TicketSystemG implements TicketG{ @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 "메뉴정보를 DB에서 가져왔습니다."; } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class TicketMachine { public static void main(String[] args) { TicketA ticketA = new TicketSystemA(); ticketA.choice(1); ticketA.buy(); ticketA.print(); System.out.println("-----------------"); TicketG ticketG = new TicketSystemG(); ticketG.choice(1); ticketG.buyOnOffline(); ticketG.buyOnOnline(); ticketG.print(); System.out.println(ticketG.getMenu()); } } | cs |
보다시피 A사와 G사의 시스템 모두 잘 작동하는것을 볼 수 있습니다.
문제는 여기서 부터 시작됩니다.
1 2 | TicketG ticketG = new TicketSystemA(); | cs |
기존 G사의 시스템안에서 A사의 시스템이 정상적으로 돌아가야 하기 때문에, 이것을 해결하려면 A사의 인터페이스를 G사에 맞게 다시 정의해 주어야 합니다. 이것은 소스 전체의 중복을 야기합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | public class NewTicketSystem implements TicketG{ @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("지원되지 않는 기능"); } } | cs |
G사의 인터페이스로 정의한 A사의 시스템입니다. 보다시피 choice, print, buyOffline 메소드를 정의하기 위해 기존의 코드를 중복하여 사용하였습니다. (아무 의미없이 copy and paste..)
만약 이 시스템이 예제로 든 시스템이 아니라 거대한 시스템이라면.. 엄청난 비효율을 불러올것이 분명합니다.
G사와 A사의 개발팀은 어뎁터 패턴을 사용하여 이 문제를 현명하게 해결하였습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public class TicketAdapter implements TicketG{ 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("지원되지 않는 기능"); } } | cs |
위의 어뎁터를 보면, 생성자로 A사의 TicketSystemA 클래스를 받고, G사의 인터페이스를 구현하여 그대로 사용할 부분에 A사의 메소드를 호출합니다. 그리고 지원하지 않는 기능은 예외를 정의하여 처리합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class TicketMachine { public static void main(String[] args) { TicketG ticketG = new TicketAdapter(new TicketSystemA()); ticketG.choice(1); ticketG.buyOnOffline(); ticketG.print(); try { System.out.println(ticketG.getMenu()); }catch(UnsupportedOperationException e) { System.out.println("이 서비스는 G사의 다른 시스템에서 제공되는 기능입니다."); } } } | cs |
이제 G사의 인터페이스로 A사의 시스템 클래스를 성공적으로 사용할 수 있게 되었습니다.
G사의 식권 발매기에서도 A사의 시스템이 아주 잘 돌아가는 것이죠..
만약 어뎁터패턴이 없었다면 G사와 A사의 개발자들은 최소한 며칠은 밤을 새 가면서 기존의 코드를 모두 옮겨야 했겠죠..
++ 그렇다면 A사의 System 클래스와 G사의 System 클래스를 모두 상속받아 만들 수도 있지 않을까요? >> 물론 가능합니다! 하지만 자바 같은 객체지향 언어에서는 다중 상속을 지원하지 않죠..
++ 자바에서는 Vector, Stack 등 옛날 버전에서 사용하던 Enumeration을 새로운 Iterator으로 바꾸면서 어뎁터패턴을 사용하였습니다.
(자료 출처 : http://m.todayhumor.co.kr/view.php?table=animal&no=55892)