P
P
Pavlo Ponomarenko2015-07-15 14:08:11
Java
Pavlo Ponomarenko, 2015-07-15 14:08:11

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;
  }
}

As a result, the method is bloated to a fucking big switch, which does nothing but call other methods. I would like a more elegant solution. In Unity, this is implemented through Reflection (OnUpdate, etc.), but it seems to me that this is not very elegant.
Let's say we have this EventManager:
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);
}

And many, many (for each sneeze) potential events. Let's say a few hundred:
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() );
    }
}

So, how to write this lib so that you can add the necessary events? Ideally it would be, for example, if this would be implemented via method override
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() );
    }
}

Naturally, in such an implementation, the idea will not work. This, of course, can be done as a switch in OnEvent (Event e), as in the first block of code, but I would not want to write stupid code and duplicate the functionality of the language. I admit that it is implemented through reflection, but I would like to avoid it. What practices are commonly used in Java and C#?

Answer the question

In order to leave comments, you need to log in

2 answer(s)
L
Larry Underwood, 2015-07-15
@Hydro

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);
  }
}

D
dordzhiev, 2015-07-15
@dordzhiev

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");
    }
}

But this method is disgusting, never use it :) At least because for each event you need to make a separate OnEvent and Publish method, and this only works in static mode, the compiler selects the desired method at compile time, i.e. manager.Publish((Event) new MoveEvent())will not call a handler for the MoveEvent.
Such things (both in Java and in C#) are better done by reflection or by using Expression in C#. You don't have to worry about the performance of Reflection, because. it is enough to register handlers all once.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question