어댑터 패턴(Adapter Pattern)은 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 도와준다.
A 프로그램은 파일을 hwp 파일로 제공한다. 그러나 B 프로그램은 hwp 파일이 아닌 docx 파일만 열 수 있다. 이런 경우, hwp 파일을 docx로 변환하는 어댑터를 구현하고, B 프로그램이 어댑터가 변환한 파일을 사용하도록 할 수 있다.
구현
어댑터 패턴은
- Client
- Client Interface
- Service
- Adapter
로 구성된다.
Client는 기존의 프로그램이다.
Client Interface는 다른 클래스들이 Client와 작업하도록 따라야 하는 프로토콜이다.
Service는 Client가 사용하고 싶은데, 호환되지 않아 사용하지 못하는 클래스이다.
Adapter는 Service 객체를 래핑해, Client의 호출을 받아 Service가 이해할 수 있도록 변환한다.
Client Interface
// Client interface
class Animal
{
public:
virtual void makeSound() const = 0;
virtual ~Animal() {}
};
Animal Interface를 상속받는 동물 클래스들은 makeSound
를 통해 소리를 낸다.
Service
// Service class
class Dog
{
public:
void bark() const
{
std::cout << "Wild dog is barking" << std::endl;
}
};
외부에서 가져온 새 Service인 Dog
클래스는 makeSound
함수가 아닌 bark
함수를 사용한다.
Adapter Class
// Adapter class
class DogAdapter : public Animal
{
private:
Dog *dog;
public:
DogAdapter(Dog *dog) : Dog(dog) {}
void makeSound() const override
{
Dog->bark();
}
};
DogAdapter
는 makeSound
를 쓰면 bark
를 호출해 Service인 Dog
를 수정하지 않아도 문제없이 사용하도록 한다.
Client (예시)
// Client code
int main()
{
Dog* dog = new Dog();
Animal* dogAdapter = new DogAdapter(dog);
dogAdapter->makeSound();
...
return 0;
}
장점
- 단일 책임 원칙을 구현한다. Client의 로직과 변환을 분리한다.
- 개방/폐쇄 원칙을 구현한다. 기존의 Client 코드 수정 없이 새 Service를 추가할 수 있다.
단점
- 코드가 복잡해진다. 때로는 Service Class를 변형하는 것이 더 간단할 수 있다.
반응형