私有事件处理程序会破坏封装吗?

4

众所周知,私有事件处理程序可以监听来自其他类的事件。(文档中的示例总是使用私有处理程序。)

事件处理程序只是调用事件的另一个类中的私有方法。因此,从其类外部调用处理程序会破坏封装性。或者我漏掉了什么?

完整代码示例:

 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();。这是一个纯粹的学术问题,甚至是教条主义的问题。此外,我个人认为这个特性非常方便、实用,而且真的很喜欢它。这真的很酷:我们可以明确授予其他类调用我们的私有方法的权利。(我们还可以取消订阅并使用委托和事件做很多有趣的事情。)无论如何,它仍然违反了封装的纯度...或者没有呢?
附注:感谢Matthew Watson指出以下细微差别:当订阅事件时,私有处理程序只能被该事件专属调用。而如果我们将其设为公共(或通过公共包装器方法调用),任何人都可以调用它。这在可访问性上有很大的区别。
另外,是的-我从来没有在教科书中看到这个问题。如果你知道一本,请留下参考资料。

2
在我看来,这并不比调用一个公共方法,然后继续调用一个私有方法更破坏封装性。 - Matthew Watson
Matthew Watson:并不完全一样。如果我们从公共方法(当然是在同一个类中)调用私有处理程序,那么任何人都可以调用此公共方法,因此也可以调用处理程序。而当订阅事件时,私有处理程序只能由此事件专属调用。 - Alexis Pokrovski
是的,但只是因为具有私有方法的类这样做。这是一个实现细节,从Receiver类外部不可观察。除了使用反射之外,没有其他类可以直接调用MyPrivateHandler - Matthew Watson
当然可以。顺便提一下,这回答了一个问题:“如何从类外调用私有方法”,尽管使用了特殊的方式。再次强调,正式的私有性是有漏洞的。虽然对于明确声明的Mickey Mouse非常有用且专门设计,但它仍然有漏洞 :) - Alexis Pokrovski
3个回答

0

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

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类中调用它? - Alexis Pokrovski

0

显然,当你从两个类的外部调用caller.RaiseMyEvent()时,你不必知道谁将处理事件。

事实上,我认为接收器中的Subscribe方法不应该是公共的,这有点违背了它的目的。如果接收器有兴趣处理事件,它应该订阅自己,而不是让其他人订阅它。这样可以保持订阅状态和处理它的方法的隐藏。

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

这会影响封装性,但我认为它并没有完全违反封装性。它基本上是一个广播系统。

如果某个电视台建议人们出去偷一个苹果,他们真的这么做了,谁应该受到责备?电视台还是偷东西的人?我想后者。

事件也是如此,引发事件的人不应该担心其影响。


也许我的例子有点误导人。重要的不是谁触发了事件 - 通常是包含事件的类中的某些内容。同样,订阅策略在包含处理程序的类中受到控制。问题在于通常情况下,私有方法可以从类外部调用。毕竟,事件处理程序只是方法 - 私有方法应该被私下调用,不是吗? - Alexis Pokrovski
我理解你的观点,但实际上你并没有从其他地方调用一个私有方法,而是引发了一个调用私有方法的事件。方法本身仍然是私有调用的,因为订阅是私有完成的。你不能通过引发事件来强制类调用其私有方法。这只是一个后效。 - Davio
不同意:“方法本身仍然被称为私有,因为订阅是私有的。” 执行流程从引发事件到订阅的私有处理程序。 订阅的私有状态非常重要,但我们只订阅一次 - 然后调用顺畅地流向(不那么私有的)方法。 - Alexis Pokrovski
但是,如果你连续引发事件100次,你就不能确定私有方法被调用了100次。它可以在事件处理程序中取消订阅自己,并且该方法再也不会被调用。因此,这个流程并没有你想象中那么直接。这就是它与通过对象调用方法的区别:object.DoMethod()被调用100次将(除非有异常等)执行100次。 - Davio
是的。无论如何,私有方法(可能是暂时的)变得可从类外调用。因此,它既不是严格私有的,也不是公共的(或受保护等)。实际上,它脱离了严格的封装方案。 - Alexis Pokrovski
我同意这一点,它脱离了封装的方案。当使用事件“总线”(如Unity/Prism的EventAggregator)时,情况甚至更糟。这基本上意味着你可以从应用程序的一端发送事件直到另一端,代码变成了一团乱麻! - Davio

0

谢谢您提供的参考资料。实际上,我只是浏览了一下(抱歉),所以无法详细评判。 - Alexis Pokrovski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接