Design Pattern

관찰자 (Observer)

coucou3 2020. 7. 5. 22:50
반응형

게임 프로그래밍 패턴 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