Answer the question
In order to leave comments, you need to log in
How to write an EventManager/Observer for many events without Reflection?
Let's say we have an Observer and many, many different events (hundreds). The standard Observer described in Wikipedia offers to distinguish them by a string identifier. You may remember that we want to use all the features of the IDE and introduce some kind of Enum instead of a string identifier, but I noticed that as a result the Update method bloats up to something like this switch:
void Update (Event e) {
switch (e.type) {
case EventType.Move:
OnMove( (MoveEvent) e );
break;
// ...
case EventType.Build:
OnBuild( (BuildEvent) e );
break;
}
}
class EventManager {
List<IListener> listeners = new List<IListener>();
public void Subscribe (IListener listener) {
listeners.Add( listener );
}
public void Publish (Event e) {
foreach (IListener listener in listeners) {
listener.OnEvent( e );
}
}
}
class Event {
public virtual string GetTitle () {
return "Title:Event";
}
}
interface IListener {
void OnEvent (Event e);
}
class BuildEvent : Event {
public override string GetTitle () {
return "Title:BuildEvent";
}
}
class MoveEvent : Event {
public override string GetTitle () {
return "Title:MoveEvent";
}
}
class AttackEvent : Event {
public override string GetTitle () {
return "Title:AttackEvent";
}
}
class Program {
static void Main() {
Console.WriteLine("Hello, World!");
EventManager manager = new EventManager();
// Хендлеров может быть много и разным хендлерам нужны разные события
// Скажем, меню построек не реагирует на базовые действия юнитов
// А модулю статистики не важны факты об окончании ремонта
manager.Subscribe( new GameHandler() );
manager.Subscribe( new MenuHandler() );
manager.Subscribe( new CostHandler() );
manager.Subscribe( new LogHandler() );
// ну и тут эти события постоянно запускаются, допустим, это длительная сессия игры
manager.Publish( new BuildEvent() );
manager.Publish( new MoveEvent() );
manager.Publish( new BuildEvent() );
manager.Publish( new BuildEvent() );
manager.Publish( new AttackEvent() );
}
}
class GameHandler : IListener {
// Все события, которые зашли сюда - игнорируются
public void OnEvent (Event e) {}
// Необходимые события переопределяю отдельными методами через аргумент функции
public void OnEvent (MoveEvent e) {
Console.WriteLine( "Foo.OnMoveEvent: " + e.GetTitle() );
}
public void OnEvent (BuildEvent e) {
Console.WriteLine( "Foo.OnBuildEvent: " + e.GetTitle() );
}
}
Answer the question
In order to leave comments, you need to log in
C# already has an event mechanism that hides the Observer pattern under the hood.
See event keyword.
In general, the switch in the Update method can be replaced with the ServiceLocator pattern, in fact
public class EventManager
{
Dictionary<EventType, EventHandler> handlerLocator.
public void RegisterEventHandler(EventType type, EventHandler handler)
{
this.handlerLocator[type] = handler;
}
public void Update(Event e)
{
var handler = this.handlerLocator[e.Type];
handler(this, e.EventArgs);
}
}
You can write an abstract class that will have empty implementations of OnEvent for each event, and in the heirs they will already be overridden if necessary. For example like this:
class EventManager
{
List<EventListenerBase> listeners = new List<EventListenerBase>();
public void Subscribe(EventListenerBase listener)
{
listeners.Add(listener);
}
public void Publish(Event e)
{
foreach (EventListenerBase listener in listeners)
{
listener.OnEvent(e);
}
}
public void Publish(MoveEvent e)
{
foreach (EventListenerBase listener in listeners)
{
listener.OnEvent(e);
}
}
public void Publish(BuildEvent e)
{
foreach (EventListenerBase listener in listeners)
{
listener.OnEvent(e);
}
}
}
abstract class EventListenerBase
{
public virtual void OnEvent(Event e)
{
}
public virtual void OnEvent(MoveEvent a)
{
}
public virtual void OnEvent(BuildEvent a)
{
}
}
class GameHandler : EventListenerBase
{
public override void OnEvent(Event e)
{
Debug.WriteLine("Event");
}
public override void OnEvent(BuildEvent a)
{
Debug.WriteLine("BuildEvent");
}
public override void OnEvent(MoveEvent a)
{
Debug.WriteLine("MoveEvent");
}
}
manager.Publish((Event) new MoveEvent())
will not call a handler for the MoveEvent. Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question