본문 바로가기
DesignPattern

[Design Pattern] Adapter Pattern

by 봄석 2019. 10. 7.

[Design Pattern] Adapter Pattern

 

Adapter Pattern이란?

어댑터 패턴은 이름대로 어댑터 같은 역할을 합니다.

한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환하는 패턴입니다.

어댑터 패턴을 사용하면 다른 인터페이스와 호환성 문제를 해결할 수 있습니다.

 

실생활의 예로 해외여행을 갈 때 외국에서는 한국과 다른 전기 규격의 플러그를 사용하는 곳이 있습니다.

그럴 때 보통 돼지코라 불리는 플러그 변환 어댑터를 사용하곤 합니다.

 

위와 같은 돼지코가 특정 규격을 다른 규격으로 호환 가능하도록 바꿔주듯이 어댑터 패턴도 같은 역할을 한다고 보면 됩니다.

 

adpater pattern 구조

Client
써드파티 라이브러리나 외부 시스템을 사용합니다.

Adaptee
써드파티 라이브러리나 외부시스템을 의미합니다.

Target Interface
Adapter 가 구현(implements) 하는 인터페이스입니다.

클라이언트는 Target Interface를 통해 Adaptee 인 써드파티 라이브러리를 사용하게 됩니다.

Adapter
Client와 Adaptee 중간에서 호환성이 없는 둘을 연결시켜주는 역할을 담당합니다.

Target Interface를 구현하며, 클라이언트는 Target Interface 를 통해 어댑터에 요청을 보냅니다.

어댑터는 클라이언트의 요청을 Adaptee 가 이해할 수 있는 방법으로 전달하고, 처리는 Adaptee에서 이루어집니다.

 

 

자바 코드로 바로 예시를 보도록 하겠습니다

식권 티켓 시스템(Java Adapter Pattern Ex)

  1. A사의 식권티켓시스템
    • 식권 선택
    • 식권 구매
    • 식권 출력(오프라인)
  2. B사의 식권 티켓 시스템
    • 식권 선택
    • 식권 오프라인 구매
    • 식권 온라인 구매
    • 식권 출력
    • 메뉴 리스트 보기

위처럼 A사와 B사 각각 개별의 식권 발권 시스템을 가지고 있습니다.

B사가 A사를 인수하게 되어 시스템을 통합하려고 합니다.

B사는 A사의 시스템을 그대로 유지하며 사용하려 합니다.

 

 

기존의 A사의 시스템 코드

public interface MealTicketA {
    public void choice(int token);

    public void buy();

    public void print();

}
public class MealTicketSystemA implements  MealTicketA{
    @Override
    public void choice(int token) {
        System.out.println("selected meal ticket type is "+ token);
    }

    @Override
    public void buy() {
        System.out.println("meal ticket buying...");
    }

    @Override
    public void print() {
        System.out.println("meal ticket printing...");
    }
}

기존의 B사의 시스템 코드

public interface MealTicketB {
    public void choice(int token);

    public void print();

    public void buyOffLine();

    public void buyOnline();

    public String getMenue();
}
public class MealTicketSystemB implements MealTicketB{
    @Override
    public void choice(int token) {
        System.out.println("selected meal ticket type is "+ token);
    }

    @Override
    public void print() {
        System.out.println("meal ticket printing...");
    }

    @Override
    public void buyOffLine() {
        System.out.println("meal ticket buying(offline)...");
    }

    @Override
    public void buyOnline() {
        System.out.println("meal ticket buying(online)...");
    }

    @Override
    public String getMenue() {
        return "todays menus list";
    }
}

기존의 티켓 시스템을 아래와 같이 확인해보면 결과는 잘 나옵니다 

public class TicketMachine {
    public static void main(String[] args) {
        MealTicketSystemA ticketSystemA = new MealTicketSystemA();
        ticketSystemA.choice(1);
        ticketSystemA.buy();
        ticketSystemA.print();

        System.out.println();

        MealTicketSystemB ticketSystemB = new MealTicketSystemB();
        ticketSystemB.choice(2);
        ticketSystemB.buyOffLine();
        ticketSystemB.buyOnline();
        ticketSystemB.print();
        System.out.println(ticketSystemB.getMenue());
    }
}
selected meal ticket type is 1
meal ticket buying...
meal ticket printing...

selected meal ticket type is 2
meal ticket buying(offline)...
meal ticket buying(online)...
meal ticket printing...
todays menus list

 


B사가 A사를 인수하면서  문제가 발생하게 되는데 

B사의 시스템 안에서 A사의 티권발권이 정상적으로 되야하기 때문에 A사의 인터페이스를 B사에 맞게 

다시 정의해주어야 하는 하게 됩니다.

 

아래처럼 말이죠

public class NewTicketSystemA implements MealTicketB {

    @Override
    public void choice(int token) {
        System.out.println("selected meal ticket type is " + token);
    }

    @Override
    public void print() {
        System.out.println("meal ticket printing...");
    }

    @Override
    public void buyOffLine() {
        throw new UnsupportedOperationException("not support function (buying offline");
    }

    @Override
    public void buyOnline() {
        throw new UnsupportedOperationException("not support function (buying online");
    }

    @Override
    public String getMenue() {
        return "todays menus list";
    }
}

위 와같이 전부 재작성을 하는 것은 엄청난 비효율입니다.

 

어댑터 패턴을 사용하여 해결해보도록 하겠습니다.

 

public class TicketAdapter implements MealTicketB {
    private MealTicketA ticket;

    public TicketAdapter(MealTicketA ticket) {
        this.ticket = ticket;
    }

    @Override
    public void choice(int token) {
        ticket.choice(token);
    }

    @Override
    public void print() {
        ticket.print();
    }

    @Override
    public void buyOffLine() {
        ticket.buy();
    }

    @Override
    public void buyOnline() {
        throw new UnsupportedOperationException("not support function (buying online");
    }

    @Override
    public String getMenue() {
        return "todays menus list";
    }
}
public class TicketMachine {
    public static void main(String[] args) {
        System.out.println("new ticket machine");

        MealTicketB newTicketSystem = new TicketAdapter(new MealTicketSystemA());

        newTicketSystem.choice(1);
        newTicketSystem.buyOffLine();
        newTicketSystem.print();
        try {
            System.out.println(newTicketSystem.getMenue());
        } catch (UnsupportedOperationException e) {
            System.out.println("this service is not support");
        }

    }
}
new ticket machine
selected meal ticket type is 1
meal ticket buying...
meal ticket printing...
todays menus list

B사의 인터페이스로 A사의 티퀀발권을 할 수 있게 됩니다.

 

JDK에 사용된 어댑터 패턴(JAVA InpustStreamReader)

콘솔에서 입력받기 위해 아래와 같이 쓰는 경우를 종종 볼 수 있습니다.

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

BufferReader 클래스의 생성자를 보면 아래와 같이 Reader 타입을 받는 것을 알 수 있습니다.

public BufferedReader(Reader in) {
    this(in, defaultCharBufferSize);
}

System.in은 여기서 InputStream타입을 반환하게 됩니다.

public final static InputStream in = null;

자바의 InputStream은 ByteStream을 읽어 들이지만, BufferedReader는 CharacterInputStream을 읽어 들입니다.

위와 같이 System.in의 InputStream과 BufferedReader는 타입이 호환되지 않지만 ,

둘을 연결해주는 어댑터가 있으니 

바로 맨 위의 코드 예에 있던 InputStreamReader입니다.

 

 

클래스 관계도를 보면 BufferedReader클래스는 Reader클래스를 상속받습니다.

InputStreamReader클래스도 Reader를 상속받습니다.

둘 다 Reader클래스의 서브클래스이므로 Reader 타입으로 참조할 수 있는 것입니다.

 

그리고 InputStreamReader 클래스는 InputStream 타입을 넘겨받는 생성자를 가지고 있으므로,

System.in을 InputStreamReader 인스턴스 생성 시에 넘겨주는 방식입니다.

 

 

Adapter  - InputStreamReader

Adaptee - System.in

Target - Reader 

라고 할 수 있습니다.

 

 

정리

Adapter Pattern이란 B를 A처럼 포장하여 사용할 수 있도록 해주는 패턴입니다.

 

 

소스코드 보러 가기

https://github.com/qjatjr1108/DesignPattern

 

qjatjr1108/DesignPattern

DesignPattern Sample. Contribute to qjatjr1108/DesignPattern development by creating an account on GitHub.

github.com

 

 

댓글