이벤트 버스(Event Bus) 패턴은 옵저버패턴과 같이 상태 변화 이벤트를 활용해 특정한 동작을 수행하게한다는 공통점이 있지만 이벤트를 관리하는 방식과 확장성에서 차이가 난다.
| 이벤트 버스 | 옵저버 |
|---|---|
![]() | ![]() |
옵저버 패턴은 주체와 구독자가 직접적으로 연결되지만, 이벤트 버스 패턴에서는 중간 관리자인 이벤트 버스가 있어서 발행자는 단순히 이벤트 버스에 이벤트를 전달하고, 구독자는 원하는 이벤트를 등록한다. 따라서 이벤트 버스 패턴에서의 발행자와 구독자는 서로의 객체 정보를 몰라도 된다.
먼저 이벤트버스 클래스를 만들어보자.
Dictionary 콜렉션을 활용해서 이벤트 이름에 따라서 Action을 관리하도록 한다.
또한, Publish 메서드로 발행자가 이벤트를 등록하고 실행하며 Subscribe, Unsubscribe를 통해 구독자가 이벤트를 구독하거나 해제할 수 있다.
public class EventBus
{
private static EventBus instance;
private readonly Dictionary<string, Action> eventTable
= new Dictionary<string, Action>();
private EventBus() {}
public static EventBus Instance => instance ??= new EventBus();
// 구독
public void Subscribe(string eventName, Action listener)
{
if (eventTable.ContainsKey(eventName))
{
eventTable[eventName] += listener;
}
else
{
eventTable[eventName] = listener;
}
}
// 구독 해제
public void Unsubscribe(string eventName, Action listener)
{
if (eventTable.ContainsKey(eventName))
{
eventTable[eventName] -= listener;
if (eventTable[eventName] == null) eventTable.Remove(eventName);
}
}
// 이벤트 등록 및 작동
public void Publish(string eventName)
{
if (eventTable.TryGetValue(eventName, out var action))
{
action?.Invoke();
}
}
}
이제 이 이벤트 버스를 활용해 적을 처치할 때마다 점수가 표시되는 Score와 Notice 클래스를 만들어보자. 당연하지만 Player 클래스도 만들어서 적 처치 이벤트를 발생시킬 수도 있어야한다.
public class Score
{
private int score = 0;
public Score()
{
EventBus.Instance.Subscribe("EnemyKilled", OnEnemyKilled);
}
private void OnEnemyKilled()
{
score += 10;
Console.WriteLine($"현재 점수: {score}");
}
}
public class Notice
{
public NotificationSystem()
{
EventBus.Instance.Subscribe("EnemyKilled", OnEnemyKilled);
}
private void OnEnemyKilled()
{
Console.WriteLine("Notice 적 처치");
}
}
public class Player
{
public void KillEnemy()
{
Console.WriteLine("Player가 적을 공격했습니다.");
EventBus.Instance.Publish("EnemyKilled"); // EnemyKilled 이벤트 시작
}
}
Main 함수에서 점수판과 공지, 플레이어 클래스를 선언해주고 KillEnemy() 메서드를 실행해보자.
이벤트가 정상적으로 작동하는 것을 알 수 있다.
public class Program
{
public static void Main(string[] args)
{
// 시스템 초기화
Score score = new Score();
Notice notice = new Notice();
// 플레이어가 적을 처치
Player player = new Player();
player.KillEnemy(); // 적 처치 시 이벤트 발행
}
}
이벤트 버스 패턴을 활용하면 발행자와 구독자가 서로 직접 참조하지 않고도 통신할 수 있어 결합도가 낮아지면서도 다수의 구독자가 동시에 하나의 특정 이벤트에 반응하게 할 수 있는 유연성과 확장성을 갖추게되지만 디버깅이 어려워지며 이에 따른 의도치 않은 동작을 방지하기 위해 잘 관리해주어야 한다.