【问题标题】:Do private event handlers break encapsulation?私有事件处理程序会破坏封装吗?
【发布时间】:2013-04-16 08:15:30
【问题描述】:

每个人都知道私有事件处理程序可以监听来自其他类的事件。 (文档中的示例总是只使用私有处理程序。)

事件处理程序只不过是另一个类中的私有方法,而不是调用事件。因此,从其类外部调用处理程序会破坏封装。还是我错过了什么?

示例代码,为了完整性:

 class Caller    {
    public event EventHandler MyEvent;
    public void RaiseMyEvent()
    {
       MyEvent(this, EventArgs.Empty);
    }
}

class Receiver
{
    private void MyPrivateHandler(Object sender, EventArgs e)
    {
        Console.WriteLine("I'm a private method!");
    }

    public void Subscribe(Caller caller)
    {
        caller.MyEvent += this.MyPrivateHandler;
    }
}

订阅receiver.Subscribe(caller);后,我们可以从外部轻松调用receiver类中的私有方法:caller.RaiseMyEvent();

这是一个纯粹的学术问题,甚至是学术问题。而且,我个人觉得这个功能非常方便、实用,而且非常喜欢。这真的很酷:我们可以显式地授予其他类调用我们私有方法的权利。 (我们也可以取消订阅它,用代表和事件制作很多有趣的东西。)不管怎样,它仍然违反了封装的纯洁性……还是不?

P.S.:感谢 Matthew Watson 指出以下细微差别:订阅事件时,该事件可以独占调用私有处理程序。而如果我们将其公开(或通过公共包装方法调用),任何人都可以调用它。可访问性有很大不同。

P.P.S:是的 - 我从未在教科书中看到过这个问题。如果你知道,请留下参考。

【问题讨论】:

  • 它不会破坏封装,就像调用一个公共方法会继续调用一个私有方法,IMO。
  • 马修·沃森:不完全一样。如果我们从公共方法(当然在同一个类中)调用私有处理程序,那么任何人都可以调用这个公共方法,因此,处理程序。而当订阅事件时,私有处理程序可以被该事件独占调用。
  • 是的,但这只是因为具有私有方法的类正在这样做。这是一个在Receiver 类之外无法观察到的实现细节。另一个类没有办法直接调用MyPrivateHandler(当然,除了使用反射)。
  • 当然。顺便说一句,它回答了“如何从类外部调用私有方法”的问题,尽管是以一种特殊的方式。再一次,正式的隐私有一个洞。非常有用且真正为明确声明的米老鼠而设计,它仍然是一个洞:)

标签: c# events delegates encapsulation


【解决方案1】:

显然,当您从任一类之外执行 caller.RaiseMyEvent() 时,您不必知道谁将处理该事件。

事实上,我不认为Receiver中的Subscribe-method应该是public的,这样就达不到目的了。如果接收者有兴趣处理事件,它应该订阅自己,它不应该让其他人订阅它。这样您就可以隐藏订阅状态和处理它的方法。

在 Receiver 类中没有直接调用这个私有方法,你不应该对此做任何假设。接收者类可以随时取消订阅。

它与封装有关,但我不认为它完全违反它。它基本上是一个广播系统。

如果某个电视台建议人们出去偷一个苹果,而他们却真的这样做了,那该怪谁?电视台还是偷东西的人?我会认为是后者。 事件也是如此,提出事件的人不必担心其影响。

【讨论】:

  • 也许我的例子有点误导。谁引发事件并不重要——通常它是在包含事件的类中进行的。同样,订阅策略在包含处理程序的类中进行控制。问题是通常可以从类外部调用私有方法。毕竟,事件处理程序只不过是方法——私有方法应该被私有调用,不是吗?
  • 我理解你的意思,但你也没有真正从其他地方调用私有方法,你正在引发一个调用私有方法的事件。该方法本身仍被私下调用,因为订阅是私下完成的。您不能通过引发事件来强制类调用其私有方法。这只是后遗症。
  • 不同意:“方法本身仍然是私有调用,因为订阅是私有完成的。” 执行流程从引发事件到订阅的私有处理程序。订阅的私有状态非常重要,但我们只订阅一次 - 然后调用顺利流向(不是很私有)方法。
  • 但是如果你连续 100 次引发事件,你永远无法确定私有方法被调用了 100 次。它可以在事件处理程序中取消订阅自己,并且永远不会再次调用该方法。所以这个流程并不像你想象的那么直接。这就是它与通过对象调用方法的不同之处:调用 100 次的 object.DoMethod() 将(排除异常等)执行 100 次。
  • 是的。无论如何,私有方法(可能是暂时的)变得可以从类外部调用。所以它既不是严格私有的,也不是公共的(或受保护的等)。实际上它不属于严格的封装方案。
【解决方案2】:

私有事件处理程序是私有的,因为您只希望为用户提供将其用作事件的选项。

private void MyPrivateHandler(Object sender, EventArgs e)
{
    Console.WriteLine("I'm a private method!");
}

public void Subscribe(Caller caller)
{
    caller.MyEvent += this.MyPrivateHandler;
}

相同
public void Subscribe(Caller caller)
{
    caller.MyEvent += (sender,e)=>{Console.WriteLine("I'm a anonymous method!"); }
}

【讨论】:

  • 不,不一样。事件处理程序只是一种方法。它对于封装是私有的,而不是因为它应该处理某些事件。也许出于其他原因我想从Receiver 类中调用它?
【解决方案3】:

虽然有点离题,但它与您的问题有关,您可能会觉得它很有趣:a link to a blog post about customizing the adding and removing of C# event handlers。 cmets 值得一读。一条相关评论将来自 MSDN 杂志的 answer ("Event Acessors" by Stephen Toub) 引用到博文问题。

【讨论】:

  • 感谢您的参考。我其实只是看了一下(对不起),所以无法详细判断。
猜你喜欢
  • 2015-02-14
  • 1970-01-01
  • 1970-01-01
  • 2011-05-10
  • 1970-01-01
  • 2011-09-19
  • 2012-11-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多