본문 바로가기
까망 동네/디자인 패턴

상태 패턴 (State Pattern)

by 까망 하르방 2022. 5. 27.
반응형

상태(State) 패턴이란?

• 객체 내부 상태에 맞춰 스스로 행동을 변경하는 패턴

• 객체는 마치 자신의 클래스를 바꾸는 것처럼 보인다.

• if-else와 같은 분기문으로 상태전이 하는 것을 해소한다.

  기존 설계 가독성이 충분하다면 굳이 State 패턴을 적용할 필요 없다.

  상태에 해당하는 클래스를 설계한다.

  상태를 취급하는 만큼 클래스 개수가 증가한다.

• 디자인 패턴(Design Pattern) 중 행위패턴에 속한다.

 

💻 디자인 패턴(Design Pattern)이란?

👨‍💻 디자인 패턴(Design Pattern)이란? • SW 개발 방법 중에서도 구조적인 문제 해결에 목적을 둔다. • 알고리즘과 같이 특정 문제를 해결하는 Logic 형태보다는    특정 상황에 적용할 수 있

zoosso.tistory.com

 


if / switch 조건 분기

게임 캐릭터 공격(attack) 방법에는

맨손•칼•총 등 다양한 무기들이 있다.

가지고 있는 무기에 따라 상태가 달라진다.

쉽게 구현할 수 있는 방법은 분기문(ifswitch)일 것이다.

#include <iostream>

using namespace std;

class Person
{
    int item;
public:
    void setItem(int item){
        this->item = item;
    }
    void attack() {
        if (item == 1)
            cout << "주먹 휘두르기" << endl;
        else if (item == 2)
            cout << "검 휘두르기" << endl;
    }
};

int main()
{
    Person* p = new Person();
    p->setItem(2);
    p->attack();
}

이 방식은 새로운 무기가 추가될 때 if문을 추가해야 한다.

attack() 함수뿐만 아니라 

다른 함수도 무기 변경 영향을 받는다면 OCP 원칙에 위배된다.

 


가상함수

변하는 부분을 가상함수로 처리해 볼 수 있다.

#include <iostream>

using namespace std;

class Person
{
    int item;
public:
    void attack() { doSth(); }
    virtual void doSth() { std::cout << "주먹 휘두르기" << std::endl; }
};

class ItemSword : public Person
{
public:
    void doSth() override { std::cout << "검 휘두르기" << std::endl; }
};

int main()
{
    Person* p = new Person;
    p->attack(); // 주먹 휟두르기

    p = new ItemSword;
    p->attack(); // 검 휘두르기
}

새로운 무기가 추가될 때,

무기(=State)에 맞는 클래스만 추가하면 된다.

기존 함수를 변경하지 않기에 OCP에 위배되지 않는다.

 

 

상태 패턴을 적용한 듯하지만

p = new ItemSword;은 상태 변경이라기 보다는

"검을 지닌 새로운 객체"를 만드는 형태라고 봐야될 것 같다.

 


인터페이스 설계

변해야 하는 멤버함수를 인터페이스로 설계

#include <iostream>

using namespace std;

struct IState
{
    virtual void attack() = 0;
    virtual void defence() = 0;
    virtual ~IState() {}
};

class Person
{
    int item;
    IState* s = nullptr;
public:
    void setState(IState* _s) { s = _s; }
    void attack() {if(s) s->attack(); }
    void defence() {if(s) s->defence(); }
};

class ItemNormal : public IState
{
public:
    void attack() override { std::cout << "기본 공격" << std::endl; }
    void defence() override { std::cout << "기본 방어" << std::endl; }
};

class ItemSword : public IState
{
public:
    void attack() override  { std::cout << "칼 휘두르기" << std::endl; }
    void defence() override { std::cout << "칼로 방어" << std::endl; }
};

class ItemGun : public IState
{
public:
    void attack() override { std::cout << "총 쏘기" << std::endl; }
    void defence() override { std::cout << "총으로 방어" << std::endl; }
};

int main()
{
    ItemNormal normal;
    ItemSword sword;
    ItemGun gun;

    Person* p = new Person;
    
    p->setState(&normal);
    p->attack();
    p->defence();

    p->setState(&sword);
    p->attack();
    p->defence();

    p->setState(&gun);
    p->attack();
    p->defence();    
}

 

가상함수와 마찬가지로 새로운 Item이 만들어지면 클래스만 추가하면 된다.

  → 하나의 객체에 연관된 함수들을 묶을 수 있다.

• 인터페이스 설계 방식은 객체 내부에서 상태만 변경하는 것으로

  기존 멤버 변수값을 유지할 수 있으며 실행시간에 변경할 수 있다.

  → 객체 행위가 내부 상태에 따라 달라할 수 있음

• 상태 변화가 되는 객체를 Context 객체라고 한다.

• 상태패턴에서 사용되는 클래스는 보통 멤버 데이터 없이

  함수만 있는 경우가 많아 싱글톤으로 구현해도 좋다.

반응형

댓글