태크놀로지

옵저버 패턴이란? 본문

디자인 패턴

옵저버 패턴이란?

원택 2020. 9. 13. 23:36

옵저버 패턴이란?

객체 사이에 일 대 다의 의존 관계를 정의해두어, 어떤 객체의 상태가 변할때 그 객체에 의존성을 가진 다른 객체들의 그 변화를 통지받고 자동으로 업데이트될 수 있게 만듭니다.  즉 한명의 상태가 변화될때 이로 인해서 변경되어야 할 다른 이벤트들을 수행해야할때 사용됩니다. 업적시스템을 예로들어 구현해보겠습니다.

 


작동원리

Observer 인터페이스

class Entity;
class Observer
{
public:
	virtual void OnNotify(int _event, const Entity& data) = 0;

public:
	Observer() = default;
	virtual ~Observer() = default;
};

Observer는 Subject에서 발생하는 이벤트를 기다리며 대기합니다. 

 

Achievements - Observer를 상속받는 클래스 (관찰자)

class Entity;
class Achievements : public Observer
{
private:
	bool m_Achieve_JumpCount = false;
	int m_JumpCount = 0;

public:
	virtual void OnNotify(int _event, const Entity& data) override;

protected:
	void UnLockJumpCount();
};
void Achievements::OnNotify(int _event, const Entity& data)
{
	switch (_event)
	{
	case EVENT_START_JUMP:
	{
		if (data.m_IsHero && !m_Achieve_JumpCount) {
			cout << "점프! ";
			if (m_JumpCount < 500)
				m_JumpCount += 1;
			else
				UnLockJumpCount();
		}
		break;
	}
	default:
		break;
	}
}

void Achievements::UnLockJumpCount()
{
	cout << "점프 500번 성공!" << endl;
	m_Achieve_JumpCount = true;
}

EVENT_START_JUMP라는 이벤트가 왔을때, 그에 대한 이벤트 처리를 수행합니다. (업적은 점프 500번시 "점프 500번 성공"이라는 업적이 해제됩니다.

 

Subject 인터페이스

class Observer;
class Entity;
class Subject
{
protected:
	std::vector<Observer*> m_Observers;

protected:
	void Notify(int _event, const Entity& data);

public:
	void AddObserver(Observer* observer);
	void RemoveObserver(Observer* observer);

public:
	virtual ~Subject() = default;
};
void Subject::Notify(int _event, const Entity& data)
{
	for (auto& o : m_Observers)
	{
		o->OnNotify(_event, data);
	}
}

void Subject::AddObserver(Observer* observer)
{
	if (!observer) return;

	m_Observers.push_back(observer);
}

void Subject::RemoveObserver(Observer* observer)
{
	if (!observer) return;

	m_Observers.erase(std::remove_if(m_Observers.begin(), m_Observers.end(), [&](Observer* _observer) {
		return _observer == observer;
		}), m_Observers.end());
}

자신을 관찰하고 있는 Observer들을 등록합니다. Observer들의 포인터를 저장하여 추가하거나, Observer을 해제할수있습니다. 이때 Observer을 추가하거나 제거하는것은 public으로 되어있어 외부에서 추가 제거가 가능합니다. 하지만 Notify와 관찰자 컨테이너는 protected로 되어있어 외부에서 접근하는것을 막습니다.

 

Physics - Subject를 상속받는 클래스 (이벤트가 발생하는 주체)

class Entity;
class Physics : public Subject 
{
private:
	bool m_OnSurface = false;

public:
	void UpdateEntity(Entity& data);

public:
	Physics() = default;
	~Physics() = default;
};
void Physics::UpdateEntity(Entity& data)
{
	m_OnSurface = data.m_IsOnSurface;

	// entity update
	data.Accelerate(1);
	data.Update();

	if (m_OnSurface && !data.m_IsOnSurface)
		Notify(EVENT_START_JUMP, data);
}

Physics에서 물리연산이 진행중에 이벤트가 발생하면, Subject의 Notify 함수를 통해 자신의 모든 관찰자들에게 Event를 보냅니다.

 

Main Entry

Physics* g_Physics;

int main(void)
{
	g_Physics = new Physics();

	Achievements* am = new Achievements();
	Entity* enti = new Entity();

	// physics에 observer 등록
	g_Physics->AddObserver(am);

	while (true) {
		// phsics update
		g_Physics->UpdateEntity(*enti);
	}

	return 0;
}

실행결과

점프가 500번 수행되고 "점프 500번 성공!" 업적이 해제된것을 볼 수 있습니다. 옵저버 패턴을 통해 물리코드와 업적해제코드 그리고 각 bool값들이 디커플링되었습니다. 하지만 일대 다의 의존관계를 보여주고 있으며 Observer와 Subject 상속을 통해 쉽게 복잡한 문제를 해결할 수 있습니다.

 


출처

도서, 게임프로그래밍 패턴, 한빛미디어

'디자인 패턴' 카테고리의 다른 글

단점을 보안한 옵저버 패턴  (0) 2020.09.15
커맨드 패턴이란?  (0) 2020.09.11
컴포넌트 패턴이란  (0) 2020.09.08
인덱스를 사용한 씬관리 방법  (0) 2020.09.03
싱글턴 패턴이란  (0) 2020.09.02