[Spring / Design Pattern] 어댑터 패턴(Adapter Pattern)

clean·2024년 2월 18일
0

개구리책

목록 보기
2/3
post-thumbnail

이 글은 책 <스프링 입문을 위한 자바 객체 지향의 원리와 이해>의 6장 스프링이 사랑한 디자인 패턴을 참고하여 작성하였습니다.

디자인 패턴은 객체 지향의 특성 중 상속(extends), 인터페이스(interface / implements), 합성(객체를 속성으로 사용)을 이용한다.

이 세 가지 방식 외에 다른 방식은 없다.

어댑터 패턴(Adapter Patteren)

어댑터 패턴을 한 문장으로 정리하면 ⭐️호출당하는 쪽의 메소드를 호출하는 쪽의 코드에 대응하도록 중간에 변환기를 통해 호출하는 패턴이다.

어댑터를 번역하는 변환기(converter)이다. 변환기의 역할은 서로 다른 두 인터페이스 사이에 통신이 가능하게 하는 것이다.

JDBC가 대표적인 어댑터이다. 여러 데이터베이스 시스템을 공통의 인터페이스인 JDBC를 통해서 조작할 수 있기 때문이다.
객체지향적 설계 SOLID를 공부하면서 자신의 변화에는 닫혀있고, 다른 객체의 변화에는 열려 있게 설계하라는 OCP(개방-폐쇄 원칙)을 접했었다. 이 어댑터 패턴은 결국 OCP를 사용한 디자인 패턴이다.

어댑터를 쓰지 않는 예시

우선 어댑터 패턴이 왜 필요한지 이해하기 위해 어댑터가 적용되지 않은 예시부터 살펴보자.

ServiceA.java

package adapterPattern;

public class ServiceA {
    void runServiceA() {
        System.out.println("ServiceA");
    }
}

ServiceB.java

package adapterPattern;

public class ServiceB {
    void runServiceB() {
        System.out.println("ServiceB");
    }
}

ClientWithNoAdapter.java

package adapterPattern;

public class ClientWithNoAdapter {
    public static void main(String[] args) {
        ServiceA sa1 = new ServiceA();
        ServiceB sb1 = new ServiceB();

        sa1.runServiceA();
        sb1.runServiceB(); // 비슷한 일을 하지만, 메소드 명이 다르다.
    }
}

ServiceA 객체의 runServiceA()와 ServiceB 객체의 runServiceB()가 비슷한 일을 하지만, 메소드 이름이 다르다.

만약 어떤 메인메소드에서 ServiceA 객체의 메소드인 sa1.runServiceA()를 호출하고 있다고 가정하자. 그런데 ServiceB 객체로 변경하고 runServiceB()를 호출하는 것으로 바꾸고 싶다면 어떻게 해야할까? runServiceA()가 쓰여있는 부분을 모두 runServiceB()로 고쳐줘야할 것이다.

하지만 어댑터를 쓰는 순간 생성하는 객체의 타입만 바꾸어주면 된다. 어떻게 그럴 수 있는지 아래에서 코드를 통해 살펴보자.

어탭터 패턴을 사용한 예시

위에서 봤던 예시 코드를 어댑터 패턴으로 고쳐보면 이렇다. 사실 고친다기보단 ServiceA, ServiceB는 가만히 두고 ServiceA, ServiceB와 메인 메소드가 있는 클래스 사이에 둘을 연결해주는 어댑터를 추가하는 것이다.

AdapterServiceA.java (새로 추가된 ServiceA의 어댑터)

package adapterPattern;

public class AdapterServiceA {
    ServiceA sa1 = new ServiceA();

    void runService() {
        sa1.runServiceA();
    }
}

AdapterServiceA.java (새로 추가된 ServiceB의 어댑터)

package adapterPattern;

public class AdapterServiceB {
    ServiceB sb1 = new ServiceB();

    void runService() {
        sb1.runServiceB();
    }
}
package adapterPattern;

public class ClientWithAdapter {
    public static void main(String[] args) {
        AdapterServiceA asa1 = new AdapterServiceA();
        AdapterServiceB asb1 = new AdapterServiceB();

        asa1.runService();
        asb1.runService(); // 어댑터를 통해 같은 메소드 명 사용 가능
    }
}

위의 예시처럼 어댑터 패턴을 통해서 runService()라는 동일한 메소드 명으로 두 객체의 메소드를 호출할 수 있게 되었다.

인터페이스로 예시 코드를 업그레이드

어댑터 인터페이스를 만들고 Adapter들이 이를 구현하게 만듦으로써 어댑터 패턴을 더 편리하게 업그레이드 할 수 있다.

ServiceRunnable.java (어댑터 인터페이스)

package adapterPattern;

public interface ServiceRunnable {
    // 참고: 인터페이스는 추상 메소드만 가질 수 있다.
    // 따라서 runService() 이렇게만 선언해도 public abstract을 자동으로 붙여준다.
    void runService();
}

AdapterServiceA.java

package adapterPattern;

public class AdapterServiceA implements ServiceRunnable { // ServiceRunnable을 구현
    ServiceA sa1 = new ServiceA();

    // 언급했다싶이, 인터페이스는 public abstract 메소드만 가진다 (디폴트 메소드 제외)
    // 따라서 여기서 runService() 메소드는 public으로 선언해주어야한다.
    public void runService() {
        sa1.runServiceA();
    }

}

AdapterServiceB.java

package adapterPattern;

public class AdapterServiceB implements ServiceRunnable{ // ServiceRunnable을 구현
    ServiceB sb1 = new ServiceB();

    public void runService() {
        sb1.runServiceB();
    }
}

ClientWithAdapter2.java

package adapterPattern;


public class ClientWithAdapter2 {
    public static void main(String[] args) {
        ServiceRunnable asa1 = new AdapterServiceA(); // 변경
        ServiceRunnable asb1 = new AdapterServiceB(); // 변경

        asa1.runService();
        asb1.runService();
    }
}

Reference

책 <스프링 입문을 위한 자바 객체 지향의 원리와 이해>

profile
블로그 이전하려고 합니다! 👉 https://onfonf.tistory.com 🍀

0개의 댓글