C#:事件或观察者界面? 优点缺点?
我有以下(简化):
interface IFindFilesObserver { void OnFoundFile(FileInfo fileInfo); void OnFoundDirectory(DirectoryInfo directoryInfo); } class FindFiles { IFindFilesObserver _observer; // ... }
…我有矛盾 这基本上是我用C ++写的,但C#有事件。 我是否应该更改代码以使用事件,还是应该让它独立?
与传统的观察者界面相比,事件的优缺点有哪些?
考虑一个事件是一个callback接口,其中接口只有一个方法。
只钩住你需要的事件
有了事件,你只需要为你有兴趣处理的事件实现处理程序。 在观察者接口模式中,您必须在整个接口中实现所有方法,包括为您实际上不关心处理的通知types实现方法主体。 在你的例子中,即使你只关心其中的一个事件,你也必须实现OnFoundDirectory和OnFoundFile。
less维护
关于事件的另一件好事是你可以给一个特定的类增加一个新的类,这样就可以提升它,而且你不需要改变每个现有的观察者。 而如果你想添加一个新的方法到一个接口,你必须绕过每一个已经实现了这个接口的类,并在它们中实现新的方法。 但是,有了一个事件,你只需要改变现有的类,这些类实际上想要做一些事情来响应你添加的新事件。
这种模式是build立在语言,所以每个人都知道如何使用它
事件是惯用的,当你看到一个事件时,你知道如何使用它。 通过观察者界面,人们通常会实现不同的注册方式来接收通知,并通过事件来连接观察者..一旦你学习了如何注册和使用(使用+ =操作符),其余的都是相同。
优点接口
我没有很多接口的优点。 我想他们强迫某人去实现界面中的所有方法。 但是,你不能强迫某人正确实施所有这些方法,所以我不认为这方面有很多价值。
句法
有些人不喜欢你必须为每个事件声明委托types的方式。 此外,.Net框架中的标准事件处理程序遵循以下参数:(object sender,EventArgs args)。 由于发件人没有指定特定types,因此如果要使用它,则必须进行下载。 这在实践中通常是很好的,如果感觉不太对,虽然因为你失去了静态types系统的保护。 但是,如果您实现自己的事件,并且不遵循.Net框架约定,则可以使用正确的types,因此不需要进行潜在的向下转换。
嗯,事件可以用来实现观察者模式。 事实上,使用事件可以被看作是观察者模式imho的另一个实现。
优点接口解决scheme:
- 如果添加方法,现有的观察者需要实现这些方法。 这意味着你有更less的机会忘记连接现有的观察员到新的function。 你当然可以把它们当作空洞的方法来实施,这意味着你对于某些“事件”的回应仍然无能为力。 但是你不会那么容易忘记。
- 如果你使用显式的实现,你也会以另一种方式得到编译器错误,如果你删除或者改变现有的接口,那么实现它们的观察者将停止编译。
缺点:
- 由于观察者界面的变化可能会对您的解决scheme进行更改,这可能需要不同的规划,所以更多思考必须进行规划。 由于一个简单的事件是可选的,除非另外的代码应该对事件作出反应,否则很less或没有其他代码必须改变。
事件的一些进一步的好处。
- 您可以免费获得适当的组播行为。
- 如果您为了响应该事件而更改事件的订阅者,则行为已被很好地定义
- 他们可以轻松而一致地反思(反映)
- 工具链支持事件(仅仅因为它们是.net中的成语)
- 您可以select使用它提供的asynchronousapis
你可以自己实现所有这些(除了工具链),但是这是非常困难的。 例如:如果使用List <>的成员variables来存储观察者列表。 如果你使用foreach来遍历它,那么任何在OnFoo()方法callback函数中添加或删除一个订阅者的尝试都将触发exception,除非你编写更多的代码来处理它。
- 事件很难通过对象链传播,例如,如果使用FACADE模式或将工作委托给其他类。
- 您需要非常小心地取消订阅事件以允许垃圾收集对象。
-
事件比简单的函数调用慢2倍,如果你对每次调用都进行空检查,则速度减慢3倍,并且在空检查和调用之前复制事件委托以使其线程安全。
-
另外阅读关于新(在4.0)
IObserver<T>
接口的MSDN。
考虑这个例子:
using System; namespace Example { //Observer public class SomeFacade { public void DoSomeWork(IObserver notificationObject) { Worker worker = new Worker(notificationObject); worker.DoWork(); } } public class Worker { private readonly IObserver _notificationObject; public Worker(IObserver notificationObject) { _notificationObject = notificationObject; } public void DoWork() { //... _notificationObject.Progress(100); _notificationObject.Done(); } } public interface IObserver { void Done(); void Progress(int amount); } //Events public class SomeFacadeWithEvents { public event Action Done; public event Action<int> Progress; private void RaiseDone() { if (Done != null) Done(); } private void RaiseProgress(int amount) { if (Progress != null) Progress(amount); } public void DoSomeWork() { WorkerWithEvents worker = new WorkerWithEvents(); worker.Done += RaiseDone; worker.Progress += RaiseProgress; worker.DoWork(); //Also we neede to unsubscribe... worker.Done -= RaiseDone; worker.Progress -= RaiseProgress; } } public class WorkerWithEvents { public event Action Done; public event Action<int> Progress; public void DoWork() { //... Progress(100); Done(); } } }
优点是事件更“点 – 净”。 如果您正在devise可拖放到表单上的非可视化组件,则可以使用devise器将其挂起。
缺点是一个事件只意味着一个单一的事件 – 你需要一个单独的事件,每个“事情”,你想通知观察员。 这实际上并没有太大的实际影响,除了每个观察对象都需要为每个事件的每个观察者持有一个参考,在存在很多被观察对象的情况下,内存膨胀(这是他们采取不同方式的原因之一pipe理WPF中的观察者/可观察关系)。
在你的情况下,我认为这并没有太大的区别。 如果观察者通常对所有这些事件感兴趣,则使用观察者界面而不是单独的事件。
Java有匿名接口的语言支持,所以callback接口是Java中使用的。
C#支持匿名代理 – lambdas – 所以事件是在C#中使用的。
接口的好处是它们更容易应用装饰器。 标准示例:
subject.RegisterObserver(new LoggingObserver(myRealObserver));
相比:
subject.AnEvent += (sender, args) => { LogTheEvent(); realEventHandler(sender, args); };
(我是装饰者模式的粉丝)。
出于以下原因,我更喜欢事件库解决scheme
- 它降低了进入的成本。 说“+ =新的EventHandler”要比实现一个完整的接口容易得多。
- 它降低了维护成本。 如果你添加一个新的事件到你的课堂,那就是所有需要完成的事情。 如果向接口添加新事件,则必须更新代码库中的每个使用者。 或者定义一个全新的界面,随着时间的推移会让消费者感到烦恼“我是否实现了IRandomEvent2或者IRandomEvent5?
- 事件允许处理程序是非基于类的(即某个静态方法)。 没有强制所有事件处理程序成为实例成员的function原因
- 将一堆事件分组成一个接口就是假定事件是如何使用的(这只是一个假设)
- 接口在原始事件中没有真正的优势。
最好的决定方式是:哪一个更适合这种情况。 这可能听起来像一个愚蠢的或无益的答案,但我不认为你应该把一个或另一个看作是“正确”的解决scheme。
我们可以向你提出一百个提示。 当观察者被期望听取任意事件时,事件是最好的。 当观察者被期望列出给定的一组事件时,接口是最好的。 处理GUI应用程序时事件是最好的。 接口消耗更less的内存(多个事件的单个指针)。 Yadda yadda yadda。 有利有弊的名单是需要思考的,但不是一个明确的答案。 你真正需要做的是在实际应用中尝试两种方式,并为他们获得一个好的感觉。 那么你可以更好地select适合的情况。 学习表单做。
如果你必须使用一个单一的定义问题,那么问问你自己哪个更能描述你的情况:一组松散相关的事件,其中任何一个都可以被使用或忽略,或者一组密切相关的事件,这些事件通常都需要被处理一名观察员。 但是,我只是描述了事件模型和接口模型,所以我又回到了一个方面:哪一个更适合这种情况呢?
如果您的对象需要以保留引用的方式进行序列化,例如使用NetDataContractSerializer,或者protobuf事件可能无法跨越序列化边界。 由于观察者模式仅仅依赖于对象引用,所以它可以与这种types的序列化一起工作,而不会出现任何问题。
防爆。 您需要将一些业务对象双向链接到需要传递给Web服务的业务对象。