Design Pattern

[Design Pattern] state pattern

sseram 2023. 5. 19. 16:55
반응형
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