[Design Pattern] 행동 패턴 2. 옵저버 (Observer)
Table of Contents
옵저버 패턴 #
- 주체와 관찰자 사이에 1 대 다 의존 관계를 정의해 둔다.
- 이때 주체의 상태가 변하면, 그 객체의 의존성을 가진 관찰자 객체들이 그 변화를 통지 받고 자동으로 갱신된다.
- 특징
- 모델-뷰-컨트롤러(Model-View-Controller: MVC) 패턴의 기반에는 옵저버 패턴이 있다.
- 워낙 흔하다보니 C#에서는
event
키워드로 지원하고, JAVA에서는java.util.Observer
에 들어가 있다. - 기존 코드를 변경하지 않고도 새로운 관찰자와 주체를 추가할 수 있다.
- 런타임에 객체 간 관계를 설정할 수 있다.
- 관찰자가 알림을 받는 순서는 보장되지 않는다.
게임에서의 예시 #
- 업적 시스템을 만들고 싶다.
- 예를 들어, ‘다리에서 떨어지기’ 업적이라면 충돌 검사 알고리즘을 통해 조건을 검사해야한다.
- 하지만 이런 물리 코드와 업적 코드를 커플링하면 안 되겠다.
- 이때 옵저버 패턴을 사용하면 좋다.
class Observer
{
public:
virtual ~Observer() {}
virtual void onNotify(const Entity& entity, Event event) = 0;
};
class Achievement : public Observer
{
private:
void unlock(Achievement achievement)
{
// 업적을 잠금해제한다.
}
public:
// 관찰자가 알림을 받는다.
virtual void onNotify(const Entity& entity, Event event)
{
switch (event)
{
case EVENT_ENTITY_FELL:
if (entity.isHero() && heroIsOnBridge)
unlock(ACHIEVEMENT_FELL_OFF_BEIDGE);
break;
// ...
}
}
};
class Subject
{
private:
int numObservers;
Observer* observers[MAX_OBSERVERS]; // 정적배열 대신 동적할당 컬렉션을 쓸 수도 있다.
protected:
// 주체의 상태가 변하면, 관찰자들에게 알림을 준다.
void notify(const Entity& entity, Event event)
{
for (int i = 0; i < numObservers; i++)
{
observers[i]->onNotify(entity, event);
}
}
public:
// 동적할당 컬렉션을 쓰는 경우, 동적 할당/해제가 잦을 수 있다.
// 그럴 때는 Observer가 다음 Observer를 가리키도록 해서 해결할 수 있겠다.
void addObserver(Observer* observer);
void removeObserver(Observer* observer);
};
class Physics : public Subject
{
public:
void updateEntity(Entity& entity)
{
bool wasOnSurface = entity.isOnSurface();
entity.accelerate(GRAVITY);
entity.update();
// 액터가 떨어졌을 때 관찰자들에게 알림을 준다.
if (wasOnSurface && entity.isOnSurface() == false)
notify(entity, EVENT_ENTITY_FELL)
}
}
개념적인 예시 #
// 주체
public interface ISubject
{
// 관찰자를 등록, 삭제하고,
// 관찰자들에게 알림을 준다.
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
public class Subject : ISubject
{
public int State { get; set; } = -0;
private List<IObserver> _observers = new List<IObserver>();
public void Attach(IObserver observer)
{
this._observers.Add(observer);
}
public void Detach(IObserver observer)
{
this._observers.Remove(observer);
}
public void Notify()
{
foreach (var observer in _observers)
{
// 관찰자들에게 알림을 준다.
observer.Update(this);
}
}
public void SomeBusinessLogic()
{
this.State = new Random().Next(0, 10);
Thread.Sleep(15);
Console.WriteLine("Subject: My state has just changed to: " + this.State);
this.Notify();
}
}
// 관찰자
public interface IObserver
{
void Update(ISubject subject);
}
class ConcreteObserverA : IObserver
{
public void Update(ISubject subject)
{
if ((subject as Subject).State < 3)
{
Console.WriteLine("A가 반응한다.");
}
}
}
class ConcreteObserverB : IObserver
{
public void Update(ISubject subject)
{
if ((subject as Subject).State == 0 || (subject as Subject).State >= 2)
{
Console.WriteLine("B가 반응한다.");
}
}
}
class Program
{
static void Main(string[] args)
{
var subject = new Subject();
var observerA = new ConcreteObserverA();
subject.Attach(observerA);
var observerB = new ConcreteObserverB();
subject.Attach(observerB);
// State == 2 -> A, B
subject.SomeBusinessLogic();
// State == 1 -> A
subject.SomeBusinessLogic();
// State == 2 -> A
subject.Detach(observerB);
subject.SomeBusinessLogic();
}
}