나를 기록하다
article thumbnail
반응형

Adapter 패턴

Adapter 패턴

제공된 것과 필요한 것 사이에 들어가서 그 사이를 채우는 것이 어댑터의 역할

이미 제공된 코드를 그대로 사용할 수 없을 때, 필요한 형태로 변환한 후 이용하는 경우가 자주 있다.

'이미 제공된 것'과 '필요한 것' 사이의 '차이'를 메우는 디자인 패턴이 바로 Adapter 패턴이다.

 

Adapter 패턴은 Wrapper 패턴이라고 불리기도 한다. 래퍼(wrapper)는 '감싸는 것'을 의미한다.

무엇인가를 포장해서 다른 용도로 사용할 수 있도록 변환해 주는 것이 래퍼이자 어댑터이다.

 

Adapter 패턴의 종류

  1. 클래스에 의한 Adapter 패턴(상속을 사용한 패턴)
  2. 인스턴스에 의한 Adapter 패턴(위임을 사용한 패턴)

예제 프로그램(1) 상속을 사용한 패턴

클래스에 의한 Adapter 패턴을 사용한 예제 프로그램을 살펴보자.

Hello라는 주어진 문자열을 다음과 같이 표시하는 간단한 프로그램이다.

(Hello)
*Hello*

 

Banner 클래스

  • showWithParen: 문자열을 괄호로 묶어서 표시하는 메서드
  • showWithAster: 문자열 앞뒤에 *를 붙여서 표시하는 메서드

Banner 클래스를 '이미 제공된 것'이라고 가정

 

Print 인터페이스

  • printWeak: 문자열을 괄호로 묶어 약하게 표시하는 메서드
  • printStrong: 문자열을 *로 강조해서 표시하는 메서드

Print 인터페이스를 '필요한 것'이라고 가정

 

PrintBanner 클래스

  • 어댑터 역할 담당
  • 제공된 Banner 클래스를 상속받아, 필요한 Print 인터페이스를 구현
  • showWithParen 메서드로 printWeak를 구현
  • showWithAster 메서드로 printStrong을 구현
  비유 예제 프로그램
제공된 것 AC 100V Banner 클래스(showWithParen, showWithAster)
변환 장치 Adapter PrintBanner 클래스
필요한 것 DC 12V Print 인터페이스(printWeak, printStrong)

 

클래스에 의한 Adapter 패턴을 사용한 예제 프로그램의 클래스 다이어그램(상속 사용)


Banner 클래스

  • 미리 제공되는 클래스
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 + "*");
    }
}

 

Print 인터페이스

  • 필요로 하는 인터페이스
public interface Print {
    public abstract void printWeak();

    public abstract void pirntStrong();
}

 

PrintBanner 클래스

  • 어댑터 역할
  • 준비된 Banner 클래스를 확장하여 showWithParen, showWithAster 메서드 상속 받음
  • 필요한 Print 인터페이스를 구현하여 printWeak, printStrong 메서드 구현
public class PrintBanner extends Banner implements Print {
    public PrintBanner(final String string) {
        super(string);
    }

    @Override
    public void printWeak() {
        showWithParen();
    }
    
    @Override
    public void printStrong() {
        showWithAster();
    }
}

 

Main 클래스

  • Print 인터페이스를 사용해서(printWeak, printStrong 메서드를 사용해서) 프로그래밍한다.
  • Banner 클래스나 showWithParen, showWithAster 메서드는 숨겨져 있다.
  • PrintBanner 클래스가 어떻게 구현됐는지 Main 클래스는 알지 못한다. 따라서 Main 클래스를 전혀 변경하지 않고도 PrintBanner 클래스의 구현을 변경할 수 있다.
public class Main {
    public static void main(String[] args) {
        Print printer = new PrintBanner("Hello");
        printer.printWeak();
        printer.printStrong();
    }
}

예제 프로그램(2) 위임을 사용한 패턴

Main 클래스와 Banner 클래스는 앞의 예제 프로그램과 같고 실행 결과도 같다. 하지만 Print는 인터페이스가 아닌 클래스라고 가정한다.

즉, Banner 클래스를 이용하여 Print 클래스와 같은 메서드를 갖는 클래스를 실현하려는 것이다. Java는 다중 상속을 지원하지 않는다. 따라서 PrintBanner 클래스를 Print와 Banner 양쪽의 하위 클래스로 정의할 수 없다.

 

PrintBanner 클래스는 banner 필드로 Banner 클래스의 인스턴스를 가진다. 이 인스턴스는 PrintBanner 클래스의 생성자에서 생성한다. printWeak, printStrong 메서드에서는 banner 필드를 통해 showWithParen, showWithAster 메서드를 호출한다.

 

이전 예제에서는 자신의 상위 클래스로부터 상속받은 showWithParen, showWithAster 메서드를 호출했지만, 이번에는 banner 필드를 경유해서 호출한다.

 

여기서 위임이 등장한다. PrintBanner 클래스의 printWeak 메서드가 호출되었을 때 자신이 처리하지 않고 다른 인스턴스(Banner의 인스턴스)인 showWithParen 메서드에 맡기는 것이다.

인스턴스를 이용한 예제 프로그램의 클래스 다이어그램(위임)


Print 클래스

public abstract class Print {
    public abstract void printWeak();

    public abstract void printStrong();
}

 

 

PrintBanner 클래스

public class PrintBanner extends Print {
    private Banner banner;

    public PrintBanner(String string) {
        this.banner = new Banner(string);
    }

    @Override
    public void printWeak() {
        banner.showWithParen();
    }

    @Override
    public void printStrong() {
        banner.showWithAster();
    }
}

Adapter 패턴의 클래스 다이어그램

클래스에 의한 Adapter 패턴의 클래스 다이어그램(상속)

클래스에 의한 Adapter 패턴의 클래스 다이어그램(상속)

 

인스턴스에 의한 Adapter 패턴의 클래스 다이어그램(위임)

인스턴스에 의한 Adapter 패턴의 클래스 다이어그램(위임)


어떤 경우에 Adapter 패턴을 사용하는 것일까

클래스의 재사용

프로그래밍할 때 늘 백지 상태에서 시작하는 것이 아니다. 이미 존재하는 클래스를 이용하는 경우도 흔하다. 특히 해당 클래스가 충분히 테스트되어 버그가 적고 실제로 지금까지 사용되어 온 실적이 있다면 더욱더 그렇다.

 

Adapter 패턴은 기존 클래스에 한겹 덧씌워 필요한 클래스를 만든다. 이 패턴을 사용하면 필요한 메서드군을 빠르게 만들 수 있다. 만약 버그가 발생하더라도 기존 클래스(Adaptee 역할)에는 버그가 없는 것을 알고 있으므로, Adapter 역할의 클래스를 중점적으로 살펴보면 되고 프로그램 검사가 매우 편해진다.

 

새로운 인터페이스(API)에 적용

이미 만들어진 클래스가 있고 새로운 인터페이스(API)에 맞춘다고 생각하면, Adapter 패턴을 사용하는 게 당연하게 느껴진다. 새로운 인터페이스(API)에 맞추려고 할 때 기존 클래스의 소스를 만져서 수정한다면 동작 테스트가 이미 끝난 기존 클래스에 손을 댔으니 수정한 후에 다시 테스트해야 한다. Adapter 패턴은 기존 클래스를 전혀 수정하지 않고 목적한 인터페이스(API)에 맞추려는 것이다. Adapter 패턴에서는 기존 클래스의 소스 프로그램이 없더라도 기존 클래스의 사양만 알면 새로운 클래스를 만들 수 있다.

 

버전 업과 호환성

레거시 시스템(legacy system)으로도 불리는 구버전을 버리면 소프트웨어 유지 보수는 편해지지만, 항상 그럴 수는 없다. Adapter 패턴은 신버전과 구버전을 공존시키고, 유지 보수까지 편하게 하도록 도와준다.

 

에를 들어, 앞으로 신버전만 유지 보수하고 싶을 때는 신버전을 Adaptee 역으로 하고, 구버전을 Target 역으로 한다. 그리고 신버전의 클래스를 사용하여 구버전의 메서드를 구현하는 Adapter 역할 클래스를 만든다.

구버전과의 호환성을 위한 Adapter 패턴

 

상속과 위임

일반적으로 상속을 사용하는 것보다 위임을 사용한느 편이 문제가 적다. 상위 클래스의 내부 동작을 자세히 모르면, 상속을 효과적으로 사용하기 어려운 경우가 많기 때문이다.

반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...