본문 바로가기
디자인 패턴

클린 소프트웨어 책에서 말하는 어탭터 패턴이란?

by simplify-len 2020. 11. 8.

디자인 패턴중 어탭터 패턴이라는 것이 있습니다. 

위키에서 말하길 어탭터 패턴은 

 어댑터 패턴(Adapter pattern)은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴으로, 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 작동하도록 해준다.

클린 소프트웨어 예시로 설명하는 어탭터 패턴을 활용하는 예시는 아래와 같습니다.

 위와같은 상황에서 Light가 서드파티에서 사온 것이라서, 소스코드가 없을 수도 있습니다. 아니면 Switch로 제어하고 싶은 다른 클래스가 있는데, 그 클래스가 Switchable로부터 파생받을 수 없다면 어떻게 해야 할까요? 여기서 ADAPTER 패턴이 등장합니다.

아래 그림은 클라이언트가 제어할 수 없는 Light 라는 클래스가 등장할 경우, 어떻게 어탭터 패턴을 활용할 수 있는지에 대한 그림입니다.

아래 그림에서 Light Adapter 는 Switchable을 상속받은 다음 실제 일은 Light에게 위임합니다. 

 

코드 참조 > gist.github.com/LenKIM/b13e77ce3b94f6e7e23f5f78482d4d9d

그러나 이렇게 중간에 Adapter 를 두는 것은 TANSTAAFL 이라는 표현을 쓴다.

이 말은 즉슨, 공짜 점심같은 건 없다 라는 것이다.

 왜냐하면, 중간에 새로운 클래스를 작성해야 하고, 어탭터를 인스턴스화한 다음, 어탭터가 중개할 객체와 어탭터를 연결하기도 해야 합니다. 그리고 어탭터를 호출할 때마다 위임 때문에 필요한 추가적인 시간과 공간이라는 대가를 지불해야 합니다. 따라서 분명 매번 어탭터를 쓰고 싶지 않을 것입니다.

 

일반적으로 어탭터 패턴은 2가지로 나뉠 수있습니다.

1. 객체 형태의 어탭터

위에서 설명한 delegates 형태의 어탭터가 객체 형태의 어탭터.

2. 클래스 형태의 어탭터

클래스 형태의 어탭터는 Lightadapter가 ThridPartLight와 Swithable을 구현받는 것을 말합니다.

아래와 같은 형태의 코드가 형성됩니다.

public class LightAdapter extends ThirdPartLight implements Switchable  {

  public LightAdapter() {
  }

  @Override
  public void turnOn() {
    super.turnOn();
  }

  @Override
  public void turnOff() {
    super.turnOff();
  }
}

이번에는 다른 예시로 어탭터 패턴을 이해해봅시다.

책 그림 25-6 모뎀 문제

여기서는 SOLID 원칙 중 LSP를 주의깊게 생각해야 합니다.

 모두 Modem 인터페이스를 사용하는 모뎀 클라이언트가 많이 있습니다. Modem 인터페이스로부터 파생되어 구현되며, OCP,LSP,DIP도 충분히 잘 지켜지고 있다고 볼 수 있습니다. 

위 형태의 모뎀에서  클라이언트의 요구사항이 생겨납니다. Modem에서 Dial(), Hangup()을 필요로 하지 않는 종류의 모뎀이 생겨날 경우, 이런 모뎀의 경우 전용 회선의 양쪽에 놓이기 때문에 전용 모뎀(dedicated modem)이라고 합니다. 이러한 전용 모뎀을 사용하는 애플리케이션을 DedUser 라고 부르겠습니다. 우리의 클라이언트는 위 Modem 인터페이스를 구현한 모뎀들과, 전용 모뎀을 모두 사용하고 싶다고 했을 경우, 어떻게 해야 될까요?

 이상적인 아키텍쳐의 형태는 위 Modem Interface를 두 개로 나눠 Dialler 인터페이스와 Modem 인터페이스를 분리해 각 구현체가 필요한 부분을 상속받아 구현 하는 것이 이상적인 해결 방안이 될 것입니다.

그러나, 만약 클라이언트가 못하게 한다면?

 사용하지 않는 Dial과 Hangup에 아무것도 하지않는 함수를 만들 확률이 높습니다.

마치 이런 형태겠죠?

public class DedicatedModem implements Modem {
	...
    @Override
    public void dial(char[] a) {
	    // Nothing Todo
    }

    @Override
    public void hangup() {
		// Nothing Todo
    }

    @Override
    public void send() {
		...
    }

    @Override
    public void receive() {
        dedicatedModem.receive();
    }
}

 그러나 이럴 경우, LSP를 위반할 수 있는 신호가 됩니다. 또한 임시방변의 해결방안일 수 있습니다.

 

그럼 어떻게 해야될까요? 

앞으로 말할 부분은 아래 위키에 정의된 목적과 일부 다른 목적의 사용 예시입니다.

 어댑터 패턴(Adapter pattern)은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 패턴으로, 호환성이 없는 인터페이스 때문에 함께 동작할 수 없는 클래스들이 함께 작동하도록 해준다.

중간에 DedicatedModemAdapter를 두고, 이를 위임받는 DedicatedModem을 만드는 것입니다. 당연히 위에서 언급한 DedUser는 DedicatedModem을 사용함으로써, 뒤에 감쳐진 DedicatedModemAdapter의 구현을 알 필요가 없습니다.

아래 클래스 다이어그램입니다.

어탭터로 모뎀 문제 해결하기

public class DedicatedModemAdapter implements Modem {
    private DedicatedModem dedicatedModem;

    public DedicatedModemAdapter(DedicatedModem delegateModem) {
        this.dedicatedModem = delegateModem;
    }

    @Override
    public void dial(char[] a) {
    }

    @Override
    public void hangup() {

    }

    @Override
    public void send() {
        dedicatedModem.send();
    }

    @Override
    public void receive() {
        dedicatedModem.receive();
    }
}
public class DedicatedModem {

    DedicatedModemAdapter dedicatedModemAdapter

	// 생성자로 받습니다..
    
    public void send() {
	dedicatedModemAdapter.send();
        System.out.println("Dedicated Modem send");
    }

    public void receive() {
	dedicatedModemAdapter.receive();
        System.out.println("Dedicated Modem receive");
    }
}

이렇게 할 때의 장점을 생각해보면, 기존의 모뎀클라이언트는 모뎀인터페이스에 의해 구현된 구현체를 사용하면 됩니다. 그렇다면 전용회선 DedicatedModem을 사용하는 입장은 어떠할까요?

 기존의 아무것도 실행하지 않는 함수를 만들어 놓은 위 DedicatedModem이 LSP를 위반할 확률이 높았습니다. 그러나, 이를 DedicatedModemAdapter 를 통해 간접적으로 DedicatedModem을 사용하게 함으로써, DedicatedModemAdapter는 연결 상태를 흉내 내기 위해 dial과 hangup을 구현하고, send와 receiver 호출은 DedicatedModem에게 위임합니다.

 그렇다면 전용모뎀을 사용하는 DedUser는 dial과 hangup을 만지작거리지 않아도 되며, 전화번호 요구사항이 변경되더라도 DedUser들은 영향받지 않습니다. 즉, 어탭터를 배치함으로써 LSP와 OCP위반을 모두 고친 것입니다.

비록 DedicatedModemAdapter가 보기 흉할 수 있다는 것이 단점이지만, 의존 관계 화살표의 방향이 어탭터에서 외부로 나가는 방향임을 주목하면, 이 설계는 썩 내키지는 않지만, 좋은 설계라고 말할수 있습니다.

댓글