[C/C++] 상태 패턴(State Pattern)

할랑말랑·2026년 3월 19일

C/C++

목록 보기
30/45

상태 패턴(State Pattern)

객체의 내부 상태가 바뀜에 따라 객체의 행동을 변경할 수 있게 해주는 행동 디자인 패턴

  • Context : 상태를 가지는 주체 객체, 클라이언트가 직접 상호작용하는 대상이며, 내부적으로 현재 상태 객체에 대한 참조를 유지, 컨텍스트는 자신의 상태가 변경되어야 할 때 다른 상태 객체로 교체할 수 있으며, 클라이언트의 요청을 받으면 현재 상태 객체에게 작업을 위임
  • State : 추상 클래스나 인터페이스로, 모든 구체적인 상태들이 구현해야 하는 공통 인터페이스를 정의, 컨텍스트가 수행할 수 있는 행동들을 추상 메서드로 선언하며, 각 상태마다 이 행동들이 다르게 구현
  • Concrete States : 인터페이스를 실제로 구현한 클래스, 필요시 컨텍스트의 상태를 다른 상태로 전환시킬 수 있다.

1. 특징

  • 상태 패턴을 사용하지 않으면 많은 if-else,switch로 상태를 체크해야 하지만, 상태 패턴은 이를 다형성으로 대체(상태 객체가 자신의 행동을 알아서 처리)
  • 개방-폐쇄 원칙 준수 : 새로운 상태를 추가할 때 기존 코드를 수정할 필요 없이 새로운 구체적 상태 클래스만 추가하면된다.
  • 단일 책임 원칙 준수 : 각 상태 클래스는 하나의 상태에 대한 책임만 가지므로, 코드의 응집도가 높고 유지보수가 용이

무기 교체 예시

  • WeaponState : State - 인터페이스
  • Magic,Melee,FirearmsWeapon : Concrete States - 인터페이스를 구현한 구체적인 상태
  • Weapon : Context - 여러 무기 상태를 가지는 객체

#include <iostream>
#include <string>

class Weapon; // 전방 선언

// 상태를 표현하는 추상 기본 클래스 (State)
class WeaponState
{
public:
    virtual ~WeaponState() = default;
    virtual void Attack(Weapon* weapon) = 0;
    virtual std::string getName() = 0;
};

// 상태를 가지는 주체 객체 (Context)
class Weapon
{
private:
    WeaponState* state; // 현재 상태를 가리키는 포인터

public:
    Weapon() : state(nullptr) {}
    ~Weapon()
    {
        delete state;
    }

    void setState(WeaponState* newState)
    {
        delete state; // 이전 상태는 삭제
        state = newState;
        if (state)
        {
            std::cout << "[ " << state->getName() << " 장착 ]" << std::endl;
        }
    }

    void Attack()
    {
        if (state)
        {
            // 실제 행동은 현재 상태 객체에게 위임
            state->Attack(this);
        }
        else
        {
            std::cout << "무기가 없습니다" << std::endl;
        }
    }

    std::string getCurrentWeapon()
    {
        return state ? state->getName() : "없음";
    }
};

// 구체적인 상태 클래스들 (Concrete States)
class FirearmsWeapon : public WeaponState
{
private:
    int ammo = 5;
public:
    void Attack(Weapon* weapon) override;
    std::string getName() override { return "총기"; }
};

class MagicWeapon : public WeaponState
{
private:
    int mana = 3;
public:
    void Attack(Weapon* weapon) override;
    std::string getName() override { return "마법"; }
};

class MeleeWeapon : public WeaponState
{
public:
    void Attack(Weapon* weapon) override
    {
        std::cout << "근접 공격!" << std::endl;
    }
    std::string getName() override { return "근접무기"; }
};

// 각 상태 클래스의 행동 정의
void FirearmsWeapon::Attack(Weapon* weapon)
{
    if (ammo > 0)
    {
        std::cout << "총기 공격! (남은 탄약: " << --ammo << ")" << std::endl;
    }
    else
    {
        std::cout << "탄약 소진! 근접 무기로 자동 전환" << std::endl;
        // 상태 스스로가 Context의 상태를 전환시킴
        weapon->setState(new MeleeWeapon());
    }
}

void MagicWeapon::Attack(Weapon* weapon)
{
    if (mana > 0)
    {
        std::cout << "마법 공격! (남은 마나: " << --mana << ")" << std::endl;
    }
    else
    {
        std::cout << "마나 소진! 근접 무기로 자동 전환" << std::endl;
        // 상태 스스로가 Context의 상태를 전환시킴
        weapon->setState(new MeleeWeapon());
    }
}

int main()
{
    Weapon weapon;

    std::cout << "=== 총기 테스트 ===" << std::endl;
    weapon.setState(new FirearmsWeapon());
    weapon.Attack();
    weapon.Attack();
    weapon.Attack();
    weapon.Attack();
    weapon.Attack(); // 탄약 소진 -> 자동 전환
    weapon.Attack(); // 근접 공격

    std::cout << "\n=== 마법 테스트 ===" << std::endl;
    weapon.setState(new MagicWeapon());
    weapon.Attack();
    weapon.Attack();
    weapon.Attack(); // 마나 소진 -> 자동 전환
    weapon.Attack();

    std::cout << "\n현재 무기: " << weapon.getCurrentWeapon() << std::endl;

    return 0;
}

  • 행동의 위임 : Weapon 클래스의 Attack 메서드는 직접 공격 로직을 가지고 있지 않다. 대신, 현재 state 포인터가 가리키는 객체(FirearmsWeapon이나 MagicWeapon 등)에게 행동을 위임한다.
  • 상태와 행동의 캡슐화: 각 무기(상태)에 따른 행동 로직과 데이터(탄약, 마나)가 해당 상태 클래스 안에 완벽하게 캡슐화되어 있습니다. Weapon 클래스는 이 세부사항을 알 필요가 없다.
  • 상태 전환: 이 패턴의 가장 강력한 특징 중 하나는 상태 객체 스스로가 다음 상태를 결정하고 전환할 수 있다는 점이다. 코드에서 보듯이 총알이나 마나가 떨어지면, FirearmsWeapon과 MagicWeapon 객체가 스스로 weapon->setState(new MeleeWeapon())를 호출하여 Weapon의 상태를 바꾼다.

0개의 댓글