옵저버패턴은 어떤 주체(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 = "눈";
}
}

이렇게 옵저버패턴을 이용하면 하나의 주체에서 각기 다른 동작을 일괄적으로 수행한다. 새로운 옵저버를 추가하고 제거하는 것도 굉장히 쉬워진다.
하지만 규모가 커지다보면 익숙하지 못한 개발자는 이런 구독관계를 추적하는 것이 어렵고 복잡할 수 있다. 그리고 메모리 누수를 막기위해서는 적재적소에 +=를 통해 구독을 시키고 -=를 통해 구독을 해제하는 것은 매우 중요하다. 옵저버가 많아질수록 알림을 보내는 비용이 증가하기도 한다.
따라서 이를 고려해 구독 관계를 명확하게 관리하고, 주체와 옵저버 간의 의존성을 최소화하는 방향으로 게임을 설계하도록 하자.