본문 바로가기
설계/디자인패턴

[디자인패턴] 어댑터(Adapter) 패턴 이해하기

by J4J 2024. 5. 3.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 어댑터 (Adapter) 패턴에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

Adapter 패턴이란?

 

adapter 패턴은 사용자가 요구하는 인터페이스 구조에 맞지 않은 클래스를 변환하여 호환되지 않은 서로 다른 인터페이스를 함께 사용할 수 있도록 도와주는 디자인 패턴입니다.

 

여기서 말하는 adapter는 일상생활에서 쉽게 접할 수 있는 어댑터와 동일한 의미를 가지고 있습니다.

 

많은 분들이 아시다시피 어댑터라고 하는 것은 서로 구조가 맞지 않은 전자 기기 등이 있을 때 어댑터를 이용하여 호환될 수 있도록 도와주는 것을 말합니다.

 

adapter 패턴도 소스 코드 내부에서 이와 같은 역할을 수행하게 됩니다.

 

서로 구조가 맞지 않은 인터페이스가 있을 때 adapter 패턴을 적용하게 된다면 함께 사용될 수 있는 결과물을 만들게 됩니다.

 

 

 

adapter 패턴을 이해하기 위해서는 다음 용어들에 대한 개념 숙지가 필요합니다.

 

  • Client → 기능을 사용하고자 하는 사용자
  • Target → Client가 사용하기 위한 기능이 담겨 있는 인터페이스
  • Adapter → Target의 구현체로 Adaptee를 상속받거나 구성 (composition) 하여 Target의 구조에 맞게 기능 정의를 하는 클래스
  • Adaptee → 실제 기능 정의가 담겨 있는 곳으로 Client가 사용하기 위한 기능과 호환되지 않는 클래스

 

 

반응형

 

 

그리고 adapter 패턴은 위에서 얘기한 Adapter 처리 방식에 따라 총 2가지의 선택지를 제공합니다.

 

첫 번째는 객체 Adapter입니다.

 

위에서 Adapter가 Adaptee를 처리하는 방식에 대해 얘기한 것 중 구성 (compoisiton)을 활용하여 구현하는 방식을 의미합니다.

 

Adapter 클래스를 정의할 때 생성자를 통해 Adaptee 클래스를 전달 받고 Adaptee 내부에 구현된 기능들을 활용하여 Client가 원하는 인터페이스 구조에 맞게 정의되도록 코드를 작성할 수 있습니다.

 

객체 Adapter의 구조는 다음과 같습니다.

 

객체 Adapter class diagram

 

 

 

Adapter 내부에 Adaptee를 변수로 관리하고 있으며 Target이 필요로 하는 메서드들을 모두 정의하는 것을 확인할 수 있습니다.

 

 

 

 

두 번째는 클래스 Adapter입니다.

 

위에서 얘기한 Adaptee 처리 방식 중 나머지 하나인 상속을 이용하여 구현하는 방식을 의미합니다.

 

객체 Adapter와 다르게 Adapter 클래스를 생성할 때 Adaptee 클래스를 상속받음으로 써 Adaptee 가 가지고 있던 기능들을 활용하여 Client가 원하는 인터페이스 구조에 맞게 정의되도록 코드를 작성할 수 있습니다.

 

클래스 Adapter의 구조는 다음과 같습니다.

 

클래스 Adapter class diagram

 

 

 

객체 Adapter와 달리 Adapter 내부에 Adaptee에 대한 변수를 관리하지 않는 것을 확인할 수 있습니다.

 

하지만 Adaptee의 모든 기능을 상속 받았기 때문에 Client가 원하는 구조에 맞게 기능 정의를 하는 것에는 문제없이 코드를 작성할 수 있습니다.

 

 

 

 

Adapter 패턴 특징

 

adapter 패턴의 특징은 다음과 같습니다.

 

 

 

[ 장점 ]

 

  • 기존 변경이 불가능한 코드를 활용하여 사용자가 원하는 새로운 인터페이스 제공 가능
  • 사용자가 원하는 구조에 맞는 Adapter 생성을 통해 유연하고 확장성 있는 개발 가능
  • 재 사용성이 높아짐

 

 

 

[ 단점 ]

 

  • 너무 많은 Adapter의 사용은 코드를 복잡하게 만드는 원인을 제공
  • Adapter 기능을 제공하기 위해 기존 기능 처리에 변환 작업까지 수행되기 때문에 성능 저하 발생

 

 

 

많은 디자인 패턴들에서 solid 원칙이 준수되는 것처럼 adapter 패턴에서도 다양한 solid 원칙이 준수되는 것도 확인할 수 있습니다.

 

Target 인터페이스를 활용하여 확장에는 열려있고 수정에는 닫혀 있기 때문에 ocp가 준수됩니다.

 

또한 기존 비즈니스 로직과 adapter 처리에 대한 기능 분리가 명확히 이루어져 있기 때문에 srp도 준수됩니다.

 

그 외에도 oop 관점에서 바라보면 상속, 다형성, 추상화 등도 모두 활용되는 것을 볼 수 있습니다.

 

 

 

 

Adapter 패턴 예시 (1) - Before

 

이번에는 adapter 패턴에 대한 예시를 들어보겠습니다.

 

간단한 예시로 기존에 개발되어 있는 speaker 기능이 존재하는데 사용자가 사용하기를 원하는 speaker application이 있다고 가정해 보겠습니다.

 

만약 이런 경우 speaker는 speaker application을 고려하여 개발된 것이 아니기 때문에 사용자 입장에서는 speaker를 다음과 같이 사용하지 못하는 경우가 발생할 수 있습니다.

 

// speaker
package com.jforj.adapter.before;

public class Speaker {
    private int sound = 1;

    public void soundUp() {
        sound++;
        System.out.println("sound up, sound is " + sound);
    }

    public void soundDown() {
        if (sound > 1) {
            sound--;
        }

        System.out.println("sound down, sound is " + sound);
    }
}


// speaker application
package com.jforj.adapter.before;

public interface SpeakerApplication {

    void clickRightButton();

    void clickLeftButton();
}


// main
package com.jforj.adapter.before;

public class Main {
    public static void main(String[] args) {
        Speaker speaker = new Speaker();
        useSpeaker(speaker); // exception !!
    }

    public static void useSpeaker(SpeakerApplication speakerApplication) {
        speakerApplication.clickLeftButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickLeftButton();
    }
}

 

 

 

 

Adapter 패턴 예시 (2) - After (객체 Adapter)

 

위에서 사용자가 speaker application을 이용하여 사용할 수 없었던 speaker를 이용할 수 있도록 adapter 패턴을 적용해 보겠습니다.

 

먼저 객체 Adapter입니다.

 

객체 Adapter를 이용하면 composition을 활용하여 Adapter 클래스 내부에 speaker 기능을 가지고 있고 이를 통해 사용자가 원하는 구조에 호환될 수 있도록 다음과 같이 코드 변경을 할 수 있습니다.

 

// speaker
package com.jforj.adapter.afterobject;

public class Speaker {
    private int sound = 1;

    public void soundUp() {
        sound++;
        System.out.println("sound up, sound is " + sound);
    }

    public void soundDown() {
        if (sound > 1) {
            sound--;
        }

        System.out.println("sound down, sound is " + sound);
    }
}


// speaker application
package com.jforj.adapter.afterobject;

public interface SpeakerApplication {

    void clickRightButton();

    void clickLeftButton();
}


// speaker adapter
package com.jforj.adapter.afterobject;

public class SpeakAdapter implements SpeakerApplication {
    private Speaker speaker;

    public SpeakAdapter(Speaker speaker) {
        this.speaker = speaker;
    }

    @Override
    public void clickRightButton() {
        speaker.soundUp();
    }

    @Override
    public void clickLeftButton() {
        speaker.soundDown();
    }
}


// main
package com.jforj.adapter.afterobject;

public class Main {
    public static void main(String[] args) {
        SpeakerApplication speakerApplication = new SpeakAdapter(new Speaker());
        useSpeaker(speakerApplication);
    }

    public static void useSpeaker(SpeakerApplication speakerApplication) {
        speakerApplication.clickLeftButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickLeftButton();
    }
}

 

 

 

 

Adapter 패턴 예시 (3) - After (클래스 Adapter)

 

이번에는 클래스 Adapter를 적용해 보겠습니다.

 

객체 Adapter와 다르게 composition이 아닌 상속을 통하여 기능 정의를 하기 때문에 다음과 같이 코드를 변경할 수 있습니다.

 

// speaker
package com.jforj.adapter.afterclass;

public class Speaker {
    private int sound = 1;

    public void soundUp() {
        sound++;
        System.out.println("sound up, sound is " + sound);
    }

    public void soundDown() {
        if (sound > 1) {
            sound--;
        }

        System.out.println("sound down, sound is " + sound);
    }
}


// speaker application
package com.jforj.adapter.afterclass;

public interface SpeakerApplication {

    void clickRightButton();

    void clickLeftButton();
}


// speaker adapter
package com.jforj.adapter.afterclass;

public class SpeakAdapter extends Speaker implements SpeakerApplication {
    @Override
    public void clickRightButton() {
        super.soundUp();
    }

    @Override
    public void clickLeftButton() {
        super.soundDown();
    }
}


// main
package com.jforj.adapter.afterclass;

public class Main {
    public static void main(String[] args) {
        SpeakerApplication speakerApplication = new SpeakAdapter();
        useSpeaker(speakerApplication);
    }

    public static void useSpeaker(SpeakerApplication speakerApplication) {
        speakerApplication.clickLeftButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickRightButton();
        speakerApplication.clickLeftButton();
    }
}

 

 

 

 

 

 

 

 

이상으로 어댑터 (Adapter) 패턴에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글