게임 프로그래밍 패턴 4장
관찰자
관찰자 패턴은 객체와 객체의 상태 변화를 관찰하는 관찰자를 일 대 다의 관계로 정의해두고, 해당 객체의 상태가 변할 때 등록된 다수의 관찰자에게 객체의 변화를 알리는 디자인 패턴이다.
예를 들어, 플레이어의 행동에 따라 다양한 업적을 달성하는 시스템이 있다고 하자. 이때 특정 행동을 하는 코드 부분 부분에 각 업적 달성 코드를 구현하게 되면 상당히 지저분해질 것이다. 스파게티처럼 이곳 저곳에 퍼진 코드는 작은 수정에도 매우 민감해진다.
관찰자 패턴을 적용한다면 플레이어가 특정 행동을 했을 때 관찰자 목록에 알림을 던져주기만 하면 된다. 업적 관찰자는 알림에 맞는 업적 달성 코드를 실행하면 된다.
관찰자(Observer)
관찰자는 객체를 관찰하다 객체의 상태 변화 메서드가 호출되면 반응하는 클래스다.
# Observer.cs
public abstract class Observer
{
public abstract void OnNotify(EventType eventType);
}
# Achievement.cs
public class Achievement : Observer
{
public override void OnNotify(EventType eventType)
{
switch (eventType)
{
case FELL_EVENT:
if (actor.isPlayer())
{
UnlockAchievement(eventType);
}
break;
}
}
// 이벤트에 해당하는 업적을 달성한다.
private void UnlockAchievement(EventType eventType)
{
}
}
대상(Subject)
관찰자의 알림 메서드는 관찰당하는 객체가 호출하고, 이 객체를 대상이라고 한다.
대상은 관찰자 목록을 갖고 있고, 관찰자를 등록하거나 제거하는 메서드를 갖고 있다.
# Subject.cs
public class Subject
{
private List<Observer> _listObservers;
...
// Observer 등록
public void AddObserver(Observer observer)
{
_listObservers.Add(observer);
}
// Observer 제거
public void RemoveObserver(Observer observer)
{
_listObservers.Remove(observer);
}
// 알림 전달
protected void Notify()
{
foreach(var observer in _listObservers)
{
observer.OnNotify();
}
}
}
# Player.cs
public class Player : Subject
{
private bool _isGrounded;
...
private void UpdatePlayer()
{
// 땅에서 떨어졌을 때 FELL_EVENT라는 알림을 보낸다.
if (_isGrounded == false)
{
Notify(EventType.FELL_EVENT);
}
}
}
관찰자 패턴은 언어 자체에 포함되어 있는 경우가 많다.
C#에서는 Delegate를 이용하여 간단히 구현할 수 있다.
# Player.cs
별다른 등록/제거 메서드 없이 delegate 변수에 관찰자의 OnNotify 메서드를 더하고 빼면 된다.
public class Player
{
delegate void EventHandler(EventType eventType);
private EventHandler _eventHandler;
...
private void UpdatePlayer()
{
if (_isGrounded == false)
{
_eventHanlder();
}
}
}
다른 예제 - 위키피디아 옵서버 패턴(https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4)
using System;
// 먼저 이벤트 발생에 사용할 델리게이트 형식을 선언한다.
// 이것은 System.EventHandler 형식과 같은 델리게이트이다.
// 이 델리게이트는 추상 옵저버로서의 기능을 제공한다.
// 어떠한 구현도 제공하지 않으며, 단지 규약만 제공한다.
public delegate void EventHandler(object sender, EventArgs e);
// 다음으로, 공개된 이벤트를 선언한다. 이것은 구체적인 서브젝트로서의 기능을 제공한다.
public class Button
{
// 공개된 이벤트를 선언한다.
public event EventHandler Clicked;
// 관습적으로, .NET 이벤트 발생은 가상 메서드로 구현된다.
// 이는 하위 클래스가 해당 이벤트를 재정의를 통해 사용할 수 있게 하고, 발생 여부도 조정할 수 있게 한다.
protected virtual void OnClicked(EventArgs e)
{
// Clicked 이벤트에 등록된 모든 EventHandler 델리게이트를 호출한다.
if (Clicked != null)
Clicked(this, e);
}
}
// 이제 옵서버 클래스에서 이벤트에 델리게이트를 등록하거나 등록 해제할 수 있다:
public class Window
{
private Button okButton;
public Window()
{
okButton = new Button();
// Clicked 이벤트에 델리게이트를 등록하는 부분이다. 등록 해제는 -= 연산자를 사용한다.
// 다수의 옵서버가 Clicked 이벤트에 델리게이트를 등록하는 것이 가능하다.
okButton.Clicked += new EventHandler(okButton_Clicked);
}
private void okButton_Clicked(object sender, EventArgs e)
{
// 이 메서드는 Button 클래스에서 Clicked(this, e) 를 호출할 때마다 실행된다.
}
}
'Design Pattern' 카테고리의 다른 글
프로토타입 (Prototype) (0) | 2020.07.19 |
---|---|
경량 (Flyweight) (0) | 2020.06.24 |
명령 (Command) (0) | 2020.06.18 |