[C/C++] 어댑터 패턴 (Adapter Pattern)

할랑말랑·2026년 3월 25일

C/C++

목록 보기
38/45

어댑터 패턴 (Adapter Pattern)

기존 클래스의 인터페이스를 클라이언트가 원하는 다른 인터페이스로 변환하여, 호환되지 않는 클래스들이 함께 동작할 수 있도록 하는 구조 패턴

1. 주요 특징

  • 기존 코드 보존 : 이미 잘 작동하는 구버전의 코드를 수정하지 않고도 새로운 시스템에 편입시킬 수 있다.
  • 중계자 역할 : 클라이언트는 어댑터만 바라보며, 어댑터가 내부적으로 구버전의 복잡한 호출을 대신 처리해 준다.

2. 구성 요소

  • Target : 클라이언트가 사용하는 인터페이스, Adapter가 구현할 인터페이스
  • Client : 타겟 인터페이스를 통해 공격 명령을 내리는 주체(Target 인터페이스만 알고 사용)
  • Adaptee : 개조가 필요한 기존 클래스 - 변경할 수 없음(라이브러리, 레거시 코드 등)
  • Adapter : 타겟을 상속받아 어댑티의 기능을 호출해 주는 연결 클래스

3. 장점

  • 관계의 분리 : 기존 비즈니스 로직(구몬스터)과 새로운 인터페이스 규격이 분리되어 유지보수가 쉽다.
  • 재사용성 : 구버전 코드를 버리지 않고 그대로 재사용할 수 있어 경제적

4. 단점

  • 복잡도 증가 : 어댑터라는 새로운 클래스가 추가되므로 전체적인 클래스 개수가 늘어남
  • 완벽한 변환 불가능 : 기능 불일치, 정보 손실 가능

객체 Adapter (Object Adapter)

  • Target : IMonster
  • Client : void MonsterAttack(IMonster* _monster, int _damage)
  • Adaptee : LegacyMonster
  • Adapter : MonsterAdapter

#include <iostream>
#include <string>

// 기존에 존재하던, 인터페이스가 다른 레거시 클래스 (Adaptee)
class LegacyMonster
{
private:
    std::string name;
public:
    LegacyMonster(const std::string& _name) : name(_name) {}
    void Attack_v1(int _damage)
    {
        std::cout << name << " - 공격 : " << _damage << std::endl;
    }
};

// 클라이언트가 요구하는 표준 인터페이스 (Target Interface)
class IMonster
{
public:
    virtual ~IMonster() = default;
    virtual void Attack(int _damage) = 0;
};

// 표준 인터페이스를 따르는 최신 클래스
class ModernMonster : public IMonster
{
private:
    std::string name;
public:
    ModernMonster(const std::string& _name) : name(_name) {}
    void Attack(int _damage) override
    {
        std::cout << name << " - 공격 : " << _damage << std::endl;
    }
};

// 레거시 클래스를 표준 인터페이스처럼 보이게 만들어주는 어댑터 클래스 (Adapter)
class MonsterAdapter : public IMonster
{
private:
    std::unique_ptr<LegacyMonster> legacy;
public:
    MonsterAdapter(std::unique_ptr<LegacyMonster> _legacy = nullptr) : legacy(std::move(_legacy)) {}

    bool connect(std::unique_ptr<LegacyMonster> _legacy)
    {
        if (legacy != nullptr)
        {
            return false;
        }
        else
        {
            legacy = std::move(_legacy);
            return true;
        }
    }

    void release()
    {
        legacy.reset();
    }

    // 표준 Attack() 호출을 받아서 내부의 LegacyMonster의 Attack_v1()으로 변환하여 호출
    void Attack(int _damage) override
    {
        if (legacy != nullptr)
        {
            legacy->Attack_v1(_damage);
        }
        else
        {
            std::cout << "연결된 몬스터가 없습니다." << std::endl;
        }
    }
};

// 클라이언트 코드: IMonster 인터페이스를 사용하는 모든 객체를 처리할 수 있음
void MonsterAttack(IMonster* _monster, int _damage)
{
    _monster->Attack(_damage);
}

int main()
{
    ModernMonster newmonster("뉴몬스터");
    MonsterAdapter adapter(std::make_unique<LegacyMonster>("구몬스터"));

    // MonsterAttack 함수는 newmonster와 adapter의 내부 구현 차이를 모르고
    // 오직 IMonster 인터페이스에만 의존하여 동일하게 처리함
    MonsterAttack(&adapter, 100);
    MonsterAttack(&newmonster, 100);

    return 0;
}

  • 인터페이스 불일치 문제 : LegacyMonster와 ModernMonster는 서로 다른 공격 메서드(Attack_v1 vs Attack)를 가지고 있다.
  • 어댑터의 역할 : MonsterAdapter는 IMonster 인터페이스를 상속받아 클라이언트(MonsterAttack 함수)의 요구사항을 만족시킨다. 그리고 내부적으로는 LegacyMonster 객체를 가지고 있다가, 자신의 Attack 메서드가 호출되면 LegacyMonster의 Attack_v1 메서드를 대신 호출
  • 클라이언트 코드의 일관성 : 어댑터 덕분에 main 함수에서는 구형 몬스터든 신형 몬스터든 구분할 필요 없이, MonsterAttack 함수를 통해 일관된 방식으로 다룰 수 있다.

0개의 댓글