如何订阅在多个类实例中引发的事件?

时间:2016-04-02 00:39:23

标签: .net oop events unity3d design-principles

我正在使用C#开发一个小型Unity项目。

我有一个类UnitManager,其中包含类Unit的实例列表。 每当属性(例如,健康)在Unit的实例内发生变化时,我都想提出一个事件。我也使用UnitManager来存储eventHandlers。例如,我有一个UI管理员订阅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上的示例只有一个“Counter”实例,但我的代码有多个实例。这意味着我必须循环遍历Unit的所有实例,然后订阅所有这些实例。我担心这会成为一个性能问题。 (特别是因为我在我的代码中的多个地方遇到了同样的问题)

总结一下我的问题:让EventHandler处理在类的多个实例中引发的事件的最佳方法(在OOP /松散耦合方面)是什么?

2 个答案:

答案 0 :(得分:2)

试试这个:

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的每个实例并监听事件。

答案 1 :(得分:1)

是否在启动时创建了侦听器,还是可以通过依赖注入容器解析它们?

另一种减少耦合并整合附加事件处理程序的方法是使用域事件总线。

你有一个单独的EventBus类 - 有些人使用静态类,我更喜欢使用IEventBus接口并将事件总线注入可能引发事件的类中。

然后,您将类注册为特定类型事件的处理程序。所以你可能有一个HealthChangedEvent类,一个IEventHandler<HealthChangedEvent>接口,然后你注册实现该接口的类。

这是关键 - 一个类被注册为事件的类型的处理程序,因此它不必订阅可能发布事件的各个类。如果任何引发HealthChangedEvent,那么每个注册的处理程序都会收到它。你正在注册一些听众,而不是订阅很多出版商。

当您的班级调用eventBus.Raise(healthChangedEvent)时,事件总线会将事件传递给每个已注册的处理程序。这样,您可以拥有与发件人分离的任意数量的侦听器。他们只知道这件事。除非对事件传递了对源的引用,否则他们不知道源。

它非常适用于依赖注入容器,因为容器可以解析IEventHandler<TEvent>

的实例

这是创建自己的blog post。我有自己的实现,允许我使用不同的DI容器而不会耦合到任何。我会把它放到博客文章中,以便分享。

更新:Here's my own implementation。我会在某些时候将它放在存储库中以及所有单元测试。