어댑터(Adapter) 패턴

weekbelt·2022년 1월 20일
1

디자인패턴

목록 보기
4/4

1. 정의

한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환합니다. 어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스들을 연결해서 쓸 수 있습니다. 요약하자면 어댑터는 한 인터페이스를 다른 인터페이스로 변환해주는 역할을 합니다.

2. 예제 코드

어댑터를 어떻게 쓰는지 예제 코드를 통해서 살펴 보겠습니다.

Duck 인터페이스를 생성합니다.

public interface Duck {

    void quack();

    void fly();
}

Duck을 구현하는 MallardDuck(물오리) 클래스를 생성합니다.

public class MallardDuck implements Duck {

    public void quack() {
        System.out.println("Quack");
    }

    public void fly() {
        System.out.println("I'm flying");
    }
}

이제 새로 등장한 가금류인 Turkey인터페이스를 생성해 보겠습니다.

public interface Turkey {

    void gobble();

    void fly();
}

Turkey의 구현체인 WildTurkey를 생성합니다.

public class WildTurkey implements Turkey {

    public void gobble() {
        System.out.println("Gobble gobble");
    }

    public void fly() {
        System.out.println("I'm flying a short distance");
    }
}

Duck 객체가 모자라서 Turkey 객체를 대신 사용해야 하는 상황이라면 Turkey를 바로 사용할 수 없고 이때 Adapter가 필요합니다.

우선 적용시킬 형식의 인터페이스를 구현해야합니다. 여기서는 클라이언트가 Turkey객체를 쓰기 원하므로 클라이언트가 원하는 인터페이스를 구현합니다.

public class TurkeyAdapter implements Duck {

    Turkey turkey;

    public TurkeyAdapter(Turkey turkey) {       // 01
        this.turkey = turkey;
    }

    public void quack() {                       // 02
        turkey.gobble();
    }

    public void fly() {                         // 03
        for (int i = 0; i < 5; i++) {
            turkey.fly();
        }
    }
}

01: 원래 형식의 객체에 대한 레퍼런스가 필요합니다. 여기에서는 생성자의 인자로 레퍼런스를 받아오는 작업을 처리합니다. 02, 03: Duck인터페이스에 있는 메소드들을 가금류의 특성에 맞게 모두 구현합니다.

import duck.Duck;
import duck.MallarDuck;
import turkey.WildTurkey;

public class DuckTestDrive {

    public static void main(String[] args) {
        MallarDuck duck = new MallarDuck();

        WildTurkey turkey = new WildTurkey();
        Duck turkeyAdapter = new TurkeyAdapter(turkey);     // 01

        System.out.println("The Tukey says...");
        turkey.gobble();
        turkey.fly();

        System.out.println("\nThe duck says...");
        testDuck(duck);

        System.out.println("\nThe TurkeyAdapter says...");
        testDuck(turkeyAdapter);
    }

    static void testDuck(Duck duck) {
        duck.quack();
        duck.fly();
    }
}

01: Turkey를 TurkeyAdapater로 감싸서 Duck처럼 보이도록 합니다.

The Tukey says...
Gobble gobble
I'm flying a short distance

The duck says...
Quack
I'm flying

The TurkeyAdapter says...
Gobble gobble
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance

테스트 결과를 보면 testDuck() 메서드에선느 오리하고 칠면조를 전혀 굽누하지 못하고 있습니다.

클라이언트가 어댑터를 사용하는 방법은

  • 클라이언트에서 타켓 인터페이스를 사용하여 메소드를 호출합니다.
  • 어댑터에서는 어댑티 인터페이스를 사용하여 그 요청을 어댑티에 대한 (하나 이상의) 메소드 호출로 변환합니다.
  • 클라이언트에서는 호출 결과를 받긴 하지만 중간에 어댑터가 껴 있는지는 전혀 알지 못합니다.

실제 Adapter 패턴을 사용하는 코드

결론

어댑터 패턴을 이용하면 호환되지 않는 인터페이스를 사용하는 클라이언트를 그대로 활용할 수 있습니다. 이렇게 함으로써 클라이언트와 구현된 인터페이스를 분리시킬 수 있으며,나중에 인터페이스가 바뀌더라도 그 변경은 어댑터에 캡슐화되기 때문에 클라이언트는 바뀔 필요가 없습니다.
하지만 대형 타겟 인터페이스를 구현해야 하는경우에는 고칠 코드가 엄청나게 많아질 수 도 있어서 고려할 사항이 많아지게 됩니다.

참고

  • Head First Design Patterns
profile
백엔드 개발자 입니다

0개의 댓글