Как подписаться на события, возникающие в нескольких экземплярах класса?

Я работаю над небольшим проектом Unity с С#.

У меня есть класс UnitManager, который содержит список экземпляров класса Unit. Я хочу вызвать событие всякий раз, когда свойство (например, здоровье) изменяется в экземпляре Unit. Я также использую UnitManager для хранения обработчиков событий. Например, у меня есть менеджер пользовательского интерфейса, который подписывается на событие в файле UnitManager.

У меня проблема в том, что я не хочу, чтобы класс Unit знал о классе UnitManager. Код, который у меня есть сейчас (ниже), работает, но я думаю, что это будет считаться связанным кодом?

public class Unit
{
    private int health;
    public int Health {
        get { return health; }
        set
        {
            health = value;
            UnitManager.Instance.OnHealthChanged(this); //Unit shouldn't call UnitManager, right?
        }
    }
}

public class UnitManager
{

    protected List<Unit> units = new List<Unit>();

    public delegate void UnitHealthChangedEventHandler(object source, UnitEventArgs args);
    public event UnitHealthChangedEventHandler HealthChanged;

    public virtual void OnHealthChanged(Unit _unit)
    {
        if (HealthChanged != null)
        {
            HealthChanged(this, new UnitEventArgs() { unit = _unit });
        }
    }
}

public class UIManager
{
    void Start()
    {
        UnitManager.Instance.HealthChanged += OnUnitHealthChanged;
    }
}

Согласно MSDN sample, обработчики событий должны храниться В Unit (отправителе события). Разница в том, что образец на MSDN имеет только один экземпляр «Счетчик», а мой код имеет несколько экземпляров. Это означало бы, что я должен перебрать все экземпляры Unit, а затем подписаться на все из них. Боюсь, это станет проблемой производительности. (Тем более, что у меня такая же проблема в нескольких местах в моем коде)

Подводя итог моему вопросу: как лучше всего (с точки зрения ООП/слабой связи) позволить обработчику событий обрабатывать события, возникающие в нескольких экземплярах класса?


person milosa    schedule 02.04.2016    source источник


Ответы (2)


Слушатели создаются при запуске или они могут быть разрешены контейнером внедрения зависимостей?

Другой подход, уменьшающий связанность и консолидирующий обработчики событий, заключается в использовании шины событий домена.

У вас есть одноэлементный класс EventBus — некоторые используют статический класс, я предпочитаю использовать интерфейс IEventBus и внедрять шину событий в класс, который может вызывать события.

Затем вы регистрируете классы как обработчики определенных типов событий. Таким образом, у вас может быть класс HealthChangedEvent, интерфейс IEventHandler<HealthChangedEvent>, а затем вы регистрируете классы, реализующие интерфейс.

В этом суть — класс регистрируется как обработчик типа события, поэтому ему не нужно подписываться на отдельные классы, которые могут публиковать события. Если что-нибудь вызывает HealthChangedEvent, то каждый зарегистрированный обработчик получает его. Вы регистрируете несколько слушателей, а не подписываетесь на множество издателей.

Когда ваш класс вызывает eventBus.Raise(healthChangedEvent), шина событий передает событие каждому зарегистрированному обработчику. Таким образом, вы можете иметь любое количество слушателей, которые не связаны с отправителем. Они знают только об этом событии. Они не знают об источнике, если с событием не передается ссылка на источник.

Он особенно хорошо работает с контейнерами внедрения зависимостей, поскольку контейнер может разрешать экземпляры IEventHandler<TEvent>.

Вот сообщение в блоге по созданию собственного. У меня есть собственная реализация, которая позволяет мне использовать разные контейнеры DI, не связываясь ни с одним из них. Я помещу это в сообщение в блоге, чтобы было легче поделиться.


Обновление: вот моя собственная реализация. В какой-то момент я положу его в репозиторий вместе со всеми модульными тестами.

person Scott Hannen    schedule 02.04.2016
comment
Спасибо за ваши ответы и сообщение в блоге. Мне потребовалось некоторое время, чтобы понять, как это реализовать. EventBus был действительно тем, что я искал. Не могли бы вы уточнить одну вещь: EventBus — это то же самое, что агрегатор событий? - person milosa; 03.04.2016
comment
Да, мне пришлось поискать, но это другой термин для того же самого. - person Scott Hannen; 03.04.2016
comment
Меня также интересует все, что я могу сделать, чтобы упростить использование и понимание. Спасибо - person Scott Hannen; 03.04.2016

Попробуй это:

public class HealthChangedEventArgs
{
    public HealthChangedEventArgs(int health) { Health = health; }
    public int Health  { get; private set; } // readonly
}

public class Unit
{
  //The delegate - event handlers must have this signature
    public delegate void HealthChangedEventHandler(object sender, HealthChangedEventArgs e);

    // The event
    public event HealthChangedEventHandler HealthChangedEvent;

    //Method for raising the event - derived classes can also call it.
    protected virtual void RaiseHealthChangedEvent()
    {
        // Raise the event by using the () operator.
        if (HealthChangedEvent != null)
            HealthChangedEvent(this, new HealthChangedEventArgs(health));
    }

    private int health;
    public int Health
    {
        get { return health; }
        set
        {
            health = value;
            RaiseHealthChangedEvent();
        }
    }
}

Unit не вызывает метод для UnitManager. Это просто поднятие события. Он не знает, что — если вообще что-нибудь — слушает.

UnitManager отвечает за добавление своего обработчика событий к каждому экземпляру Unit и прослушивание события.

person Scott Hannen    schedule 02.04.2016