어댑터 패턴이란
🌟 어떤 클래스에 사용자가 원하는 인터페이스를 구현하게 하고 싶을 떄 사용한다. 어댑터 클래스를 통해 새 인터페이스를 구현할 수 있다.
대표적인 예시로 JDBC가 있다. JDBC는 다양한 DBMS를 구현체만 바꾸어 사용하는 공통 인터페이스이다. Oravcle, MySQL은 연관성이 없지만, JDBC 인터페이스를 통해 호환 가능하다. 그리고 일관되게 동작한다.
사용하는 이유?
- 연관없는 클래스들끼리 공통 인터페이스를 구현하여, 일관되게 동작하게 한다. 또한, 공통 인터페이스를 통해 클래스들끼리 호환할 수 있다. 이로 인해 클라이언트 코드가 간단해진다.
- 이미 구현된 라이브러리에 새 기능을 추가하고 싶을 경우, 기존 코드를 변경하지 않고 새로운 인터페이스(기능)를 구현하게 한다.
동작 (JDBC 예시)
기존 클래스(Adaptee)가 새로운 인터페이스(Target)를 구현하고 싶은 상황
- 클라이언트는 Target 인터페이스(JDBC)에 의존한다.
- 이 Target 인터페이스를 Adapter 클래스가 구현한다.
- Adapter 클래스가 Adaptee 인스턴스를 감싼다. 이로 인해 Adaptee 인스턴스는 Target 인터페이스를 구현한다.
- 기존 클래스들, 즉 Adaptee 클래스들(Oracle/MySQL JDBC Driver)은 Target 인터페이스(JDBC)의 메소드들을 각자에 맞게 오버라이딩한다.
따라서 Adaptee 클래스들(Oracle/MySQL JDBC Driver)은 서로 연관성이 없지만, 일관되게 동작한다. 또한 클라이언트는 Target 인터페이스에만 의존하므로, 필요에 따라 Adaptee 를 바꿔 사용할 수 있다.
예시
예시 참고 : Bird를 구현한 Sparrow는 새롭게 ToyDuck의 기능을 구현하고 싶은 상황.
1번. Sparrow은 Bird인터페이스를 구현하는 클래스이다. (Sparrow 이 새로운 인터페이스 ToyDuck을 구현하고 싶은 상황)
interface Bird
{
public void fly();
public void makeSound();
}
class Sparrow implements Bird
{
public void fly()
{
System.out.println("Flying");
}
public void makeSound()
{
System.out.println("Chirp Chirp");
}
}
2번. Sparrow이 ToyDuck인터페이스를 구현하게 하고 싶으므로, Adapter 패턴을 사용한다. ToyDuck인터페이스를 구현한 Adapter 클래스를 생성한다.
interface ToyDuck
{
// target interface
public void squeak();
}
// 어댑터 클래스
class BirdAdapter implements ToyDuck
{
Bird bird;
public BirdAdapter(Bird bird)
{
this.bird = bird; // adaptee
}
public void squeak()
{
bird.makeSound();
}
}
3번. BirdAdapter 객체에 Sparrow 를 주입한다. (어댑터 객체에 기존 객체를 주입한다.) ⇒ Sparrow 은 ToyDuck 기능도 가진다.
Sparrow sparrow = new Sparrow();
// sparrow을 birdAdapter로 감싸서(주입해서)
// ToyDuck의 동작을 수행하게 한다.
ToyDuck birdAdapter = new BirdAdapter(sparrow);
Adapter로 감싸지는 Adaptee는 Target 인터페이스를 사용할 수 있다. 이러한 방식으로 Adapter 패턴을 사용하면 연관없는 클래스들도 공통 인터페이스(Target )를 가지게된다. 또한 코드 변경없이 원하는 인터페이스를 상속받게 한다.
Reference
- https://www.geeksforgeeks.org/adapter-pattern/?ref=gcse
- https://johngrib.github.io/wiki/pattern/adapter/