state pattern
정의 : 상태 패턴(state pattern)은 객체 지향 방식으로 상태 기계를 구현하는 행위 소프트웨어 디자인 패턴이다. 상태 패턴을 이용하면 상태 패턴 인터페이스의 파생 클래스로서 각각의 상태를 구현함으로써, 또 패턴의 슈퍼클래스에 의해 정의되는 메소드를 호출하여 상태 변화를 구현함으로써 상태 기계를 구현한다.
-> 객체가 하는 모든 동작을 교체한다. 마치 다른 클래스를 사용하는 것 같은 효과를 얻을 수 있다.
동작을 할 때, 변하지 않는 것은 그대로 두고 변하는 것을 다른 클래스로 분리하는 것이 핵심.
말로 설명하면 복잡하니, 코드를 보자.
#include <iostream>
class Character {
public:
int state;
void run() {
if (state == 1)
std::cout << "run" << std::endl;
else if (state == 2)
std::cout << "fast run" << std::endl;
else if (state == 3)
std::cout << "very fast run" << std::endl;
}
void attack() {
if (state == 1)
std::cout << "attack" << std::endl;
else if (state == 2)
std::cout << "fast attack" << std::endl;
else if (state == 3)
std::cout << "magic attack" << std::endl;
}
};
int main() {
auto p = std::make_unique<Character>();
p->state = 2;
p->run();
p->attack();
return 0;
}
위와 같은 코드가 있다고 하자. character class가 있고, state에 따라 run할 때 동작을 다르게 한다.
지금은 잘 돌아가지만, 만약 추후 item이 새롭게 추가되거나 삭제되면 매번 해당 class를 찾아가서 수정하기 힘들 것이다.
state pattern은 위 코드에 대해 아래와 같은 해결책을 내린다.
- character의 run, attack 이라는 메소드는 그대로 둔다.
- run, attack메소드를 갖는 상태 패턴 인터페이스를 정의한다.
- 해당 인터페이스를 상속받아 각각에 상태에 대해 다른 동작을 정의한다.
먼저 상태 패턴 인터페이스를 정의한다.
class iMotion {
public:
virtual void run() = 0;
virtual void attack() = 0;
};
그 후, 각각 상태에 대한 동작들을 위 인터페이스를 상속받아 정의한다.
class normalState : public iMotion {
virtual void run() override
{
std::cout << "run" << std::endl;
}
virtual void attack() override
{
std::cout << "attack" << std::endl;
}
};
class rareState : public iMotion {
virtual void run() override
{
std::cout << "fast run" << std::endl;
}
virtual void attack() override
{
std::cout << "fast attack" << std::endl;
}
};
class magicState : public iMotion {
virtual void run() override
{
std::cout << "very fast run" << std::endl;
}
virtual void attack() override
{
std::cout << "magic attack" << std::endl;
}
};
그러면 character class는 아래와 같이 state를 받고, 해당 state의 함수를 호출해 주면 된다.
class Character {
iMotion* state{nullptr};
public:
void setState(iMotion* nowstate) {
state = nowstate;
}
void run() {
if (state) state->run();
}
void attack() {
if (state) state->attack();
}
};
완성된 코드는 아래와 같다.
#include <iostream>
class iMotion {
public:
virtual void run() = 0;
virtual void attack() = 0;
};
class normalState : public iMotion {
virtual void run() override
{
std::cout << "run" << std::endl;
}
virtual void attack() override
{
std::cout << "attack" << std::endl;
}
};
class rareState : public iMotion {
virtual void run() override
{
std::cout << "fast run" << std::endl;
}
virtual void attack() override
{
std::cout << "fast attack" << std::endl;
}
};
class magicState : public iMotion {
virtual void run() override
{
std::cout << "very fast run" << std::endl;
}
virtual void attack() override
{
std::cout << "magic attack" << std::endl;
}
};
class Character {
iMotion* state{nullptr};
public:
void setState(iMotion* nowstate) {
state = nowstate;
}
void run() {
if (state) state->run();
}
void attack() {
if (state) state->attack();
}
};
int main() {
auto p = std::make_unique<Character>();
p->setState(new normalState);
p->run();
p->attack();
p->setState(new magicState);
p->run();
p->attack();
return 0;
}
이런 식으로 객체의 모든 동작을 교체하는것이 state pattern이다.
객체의 내부 상태에 따라 동작이 달라지는 경우에 유용하게 사용 가능하나, 기반이 되는 interface가 변경이 되면 수정할 점이 많아지기에(run / attack 말고 다른 method의 추가 등), 사용 전 이 부분이 앞으로도 변경이 없을지 잘 생각해보고 적용해야 한다.
'Design Pattern' 카테고리의 다른 글
flyweight pattern (0) | 2022.12.01 |
---|---|
iterator pattern (0) | 2022.11.20 |
Template Method (0) | 2022.09.06 |
Observer Pattern (0) | 2022.08.14 |
Decorator pattern (0) | 2022.07.26 |