옵저버 패턴

송칭·2024년 11월 8일
0

디자인패턴

목록 보기
5/8

옵저버패턴은 어떤 주체(Subject)에 변화가 생기면 이를 관측하는 옵저버(Observer)들에게 이를 이벤트 형태로 알리고 각 옵저버가 필요한 동작을 수행하게 만드는 디자인 패턴이다. 특히 Invoke() 메서드와 Action 델리게이트는 C#에서 옵저버패턴을 구현하는 데 자주 사용되는 기능이다.

간단한 예를 들어서 살펴보자. 어떤 게임에 날씨라는 개념이 있다면 날씨가 변경되면 이에 따라 게임 NPC나 다른 사물들의 행동이 바뀌어야한다고 가정한다. 날씨를 표시해주는 UI가 있다면 이 또한 변경되어야 한다.

public class WeatherController
{	
    public event Action<string>? OnWeatherChanged; // 날씨 변경 이벤트
	
    private string currentWeather;
    public string CurrentWeather
    {
        get => currentWeather;
        set
        {
            currentWeather = value;
            OnWeatherChanged?.Invoke(currentWeather); // 변경되면 옵저버들에게 알림
        }
    }
}

WeatherController에서는 날씨정보를 가지고 있으며 날씨가 변경되면 자신을 구독한 옵저버들에게 Invoke()를 통해 이벤트를 보낸다. Action<T>에서 T 형태는 이벤트에 실어보낼 인자의 타입을 지정하게된다.

public interface IWeatherObserver
{
    void WeatherUpdate(string weather);
}

public class UI : IWeatherObserver
{
    public void WeatherUpdate(string weather)
    {
        Console.WriteLine($"[UI] 현재 날씨 : '{weather}'");
    }
}

public class NPC : IWeatherObserver
{
    public void WeatherUpdate(string weather)
    {
        Console.WriteLine($"[상인] 오호 지금 날씨는 '{weather}'이구만!");
    }
}

public class TV : IWeatherObserver
{
    public void WeatherUpdate(string weather)
    {
        Console.WriteLine($"[텔레비전] 오늘의 날씨는 '{weather}'입니다.");
    }
}

각각의 게임 오브젝트 (옵저버)들은 날씨가 변경될 때마다 WeatherUpdate(string) 함수를 실행하기를 원한다. 이를 위해 주체의 Action에 자신의 WeatherUpdate를 구독시킨다.

public class Program
{
    public static void Main(string[] args)
    {
        // 주체(WeatherController) 생성
        WeatherController weatherController = new WeatherController();

        // 옵저버 생성
        UI weatherUI = new UI();
        NPC merchant = new NPC();
        TV tv = new TV();

        // 옵저버를 주체의 이벤트에 등록
        weatherController.OnWeatherChanged += weatherUI.WeatherUpdate;
        weatherController.OnWeatherChanged += merchant.WeatherUpdate;
        weatherController.OnWeatherChanged += tv.WeatherUpdate;

        // 날씨 정보 업데이트
        weatherController.CurrentWeather = "맑음";
        weatherController.CurrentWeather = "비";
        weatherController.CurrentWeather = "눈";
	}
}

이렇게 옵저버패턴을 이용하면 하나의 주체에서 각기 다른 동작을 일괄적으로 수행한다. 새로운 옵저버를 추가하고 제거하는 것도 굉장히 쉬워진다.

하지만 규모가 커지다보면 익숙하지 못한 개발자는 이런 구독관계를 추적하는 것이 어렵고 복잡할 수 있다. 그리고 메모리 누수를 막기위해서는 적재적소에 +=를 통해 구독을 시키고 -=를 통해 구독을 해제하는 것은 매우 중요하다. 옵저버가 많아질수록 알림을 보내는 비용이 증가하기도 한다.

따라서 이를 고려해 구독 관계를 명확하게 관리하고, 주체와 옵저버 간의 의존성을 최소화하는 방향으로 게임을 설계하도록 하자.

profile
게임 클라이언트

0개의 댓글