PostCover

디자인 패턴을 쉽게 풀어보자 - 어댑터 패턴편

GoF의 23가지 디자인 패턴 중 하나인 '어댑터 패턴(Adapter Pattern)'을 쉽게 풀어보기

어댑터(Adapter) 패턴

어댑터 패턴은 호환되지 않는 인터페이스를 가진 클래스들 사이의 호환성을 제공하는 디자인 패턴입니다. 마치 어댑터 플러그가 서로 다른 형태의 전기 소켓을 연결해주는 것처럼, 클라이언트 코드가 기존 코드나 라이브러리를 수정하지 않고 사용할 수 있도록 합니다.

문제점: 호환되지 않는 인터페이스로 인한 통합 불가능

애플리케이션에서 기존 코드나 외부 라이브러리가 제공하는 인터페이스와 클라이언트의 요구가 다를 수 있습니다. 예를 들어, 이미지 뷰어 애플리케이션에서 다양한 이미지 형식을 지원해야 하지만 각 이미지 라이브러리가 서로 다른 인터페이스를 제공합니다.

// 기존 코드: PNG 이미지 로더
class PngImage {
    public void loadPng(String filename) {
        System.out.println("Loading PNG: " + filename);
    }
}

// 클라이언트 코드: 이미지 로더 인터페이스 요구
interface ImageViewer {
    void loadImage(String filename);
}

// 클라이언트는 PngImage를 직접 사용할 수 없음
public class Main {
    public static void main(String[] args) {
        // PngImage가 ImageViewer 인터페이스를 구현하지 않기 때문에 사용할 수 없음.
        // ImageViewer viewer = new PngImage(); // 오류 발생
    }
}

문제점

  • 호환되지 않는 인터페이스: PngImage 클래스는 loadPng() 메서드를 사용하지만, 클라이언트는 loadImage() 메서드를 기대합니다.
  • 유연성 부족: 여러 이미지 형식을 동일한 인터페이스로 처리하기 어렵습니다.
  • 외부 라이브러리 코드 수정 불가: PngImage 클래스를 직접 수정하지 않고 통합해야 합니다.

해결 방법: 어댑터 패턴 적용

어댑터 패턴을 사용하면 기존 클래스의 인터페이스를 클라이언트가 기대하는 인터페이스로 변환할 수 있습니다.

// 기존 코드: PNG 이미지 로더
class PngImage {
    public void loadPng(String filename) {
        System.out.println("Loading PNG: " + filename);
    }
}

// 클라이언트가 기대하는 인터페이스
interface ImageViewer {
    void loadImage(String filename);
}

// 어댑터 클래스: PngImage를 ImageViewer로 변환
class PngImageAdapter implements ImageViewer {
    private PngImage pngImage;

    public PngImageAdapter(PngImage pngImage) {
        this.pngImage = pngImage;
    }

    @Override
    public void loadImage(String filename) {
        // 기존 PngImage 클래스의 메서드를 호출
        pngImage.loadPng(filename);
    }
}

public class Main {
    public static void main(String[] args) {
        // 기존 PngImage 객체 생성
        PngImage pngImage = new PngImage();

        // 어댑터를 사용해 PngImage를 ImageViewer 인터페이스로 변환
        ImageViewer viewer = new PngImageAdapter(pngImage);

        // 클라이언트는 동일한 인터페이스로 이미지를 로드
        viewer.loadImage("example.png");  // 출력: Loading PNG: example.png
    }
}

어댑터 패턴의 장단점

장점

  • 유연성 제공: 호환되지 않는 인터페이스를 연결해 코드를 재사용할 수 있습니다.
  • 기존 코드 수정 불필요: 기존 클래스나 라이브러리를 수정하지 않고도 사용할 수 있습니다.
  • 확장성 향상: 새로운 기능을 어댑터를 통해 쉽게 추가할 수 있습니다.

단점

  • 복잡성 증가: 어댑터 클래스를 추가해야 하므로 코드가 복잡해질 수 있습니다.
  • 성능 문제: 어댑터를 통한 호출은 직접 호출보다 약간의 성능 오버헤드가 발생합니다.
  • 클래스 수 증가: 다양한 어댑터를 사용하면 클래스가 많아질 수 있습니다.

결론

어댑터 패턴은 기존 코드와 클라이언트 코드 간의 인터페이스 차이를 해소하는 데 유용한 패턴입니다. 이 패턴을 사용하면 서로 다른 인터페이스를 가진 객체들을 통합할 수 있어 재사용성이 높아지고, 기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있습니다. 다만, 코드 복잡성이 증가할 수 있으므로 필요에 따라 신중하게 사용해야 합니다.