我正在开发一个尝试使用观察者模式的应用程序。基本上我有一个基本表单,可以从中加载各种组件(表单)。
基本形式引用每个组件,并且一些组件相互引用。
如果我希望其中一个组件监听由基本表单引发的事件(可能来自菜单等),我似乎无法在不需要在组件中添加对基本表单的引用的情况下实现这一点。这会导致“循环引用”。
是否可以收听/订阅未引用项目中的事件?
我正在开发一个尝试使用观察者模式的应用程序。基本上我有一个基本表单,可以从中加载各种组件(表单)。
基本形式引用每个组件,并且一些组件相互引用。
如果我希望其中一个组件监听由基本表单引发的事件(可能来自菜单等),我似乎无法在不需要在组件中添加对基本表单的引用的情况下实现这一点。这会导致“循环引用”。
是否可以收听/订阅未引用项目中的事件?
您可以通过多种方式解决此问题。一种简单的方法是有一个特殊的类,它知道基本形式和各种组件。这个类负责创建表单和组件。由于它知道它们,它可以将事件处理程序附加到适当的事件。它本质上只是将组件上的事件处理程序方法“插入”到基本表单上的事件中。
另一种方法是定义一个带有将由主窗体实现的事件的接口。组件可以在其构造函数中传递此接口的实例。然后它们可以附加到事件处理程序。因此组件只知道接口,而不知道基本形式。这是“依赖于抽象,而不是实现”原则的应用。在这种情况下,基本形式将实现接口并了解组件,并在构造它们时将其自身传递给它们。因此依赖是单向的。
然而,最终的解决方案是使用依赖注入容器,例如StructureMap。您将有一个配置方法,它将基表单类注册为接口的默认实现者,以及各种组件类。然后,StructureMap 可以根据需要创建类的实例,自动将接口注入到构造函数中。
Microsoft 正在开发可能对您有用的“托管可扩展性框架 (MEF)”。从 MEF 概述:
MEF 为宿主应用程序提供了一种公开自身和使用外部扩展的标准方式。扩展,就其性质而言,可以在不同的应用程序中重用。但是,扩展仍然可以以特定于应用程序的方式实现。扩展本身可以相互依赖,MEF 将确保它们以正确的顺序连接在一起(另一件事您不必担心)。
MEF 的概述和下载位于http://www.codeplex.com/MEF/Wiki/View.aspx?title=Overview&referringTitle=Home
您的框架应该定义用于连接事物的接口(请注意,事件可以在接口中定义,因此您可以将事件提升到接口)。Mike Scott 的“依赖于抽象,而不是实现”的建议是正确的,但我在这里要强一点——你应该按合同编程并使用分层设计。
或者,您可以使用像 INotifyPropertyChanged 这样的接口,它提供了一个可用于通过反射检索信息的字符串,但这是一种脆弱的工作方式,应该是最后的手段。
您是否担心循环引用或循环依赖?使用观察者模式时很难避免循环引用(运行时问题)。循环依赖(设计时问题)总是可以摆脱的。
c# 中观察者模式的典型用法是让观察者拥有对发布者对象的引用,并使用以下方式将自己注册到事件中:
publisherObject.SomeEvent += MyEventHandler();
所以观察者已经有了对 publisherObject 的引用,而在后台发生的事情是 publisherObject 被发送(并存储)了对观察者事件处理程序的引用。因此,除非您要立即删除对 publisherObject 的引用,否则您将陷入循环引用。
当您希望垃圾收集器清理未使用的对象时,这通常只是一个问题。在 publisherObject 中对观察者事件处理程序的“隐藏”引用足以防止观察者被垃圾收集。这有时被称为失效的侦听器问题。最简单的方法是将事件取消订阅放在观察者的 Dispose() 方法中(并记住在摆脱观察者时调用它)。
你可以解决这个问题,但它通常会增加很多复杂性,这在小型应用程序中可能不会得到保证。以前的海报已经建议了 EventBroker、MEF 和依赖注入路由。
如果您更关心循环依赖关系,那么最简单的答案是 Quarrelsome 建议的严格的父子层次结构。父母(观察者)总是知道它的孩子,所以如果需要的话可以直接调用他们的属性和方法。孩子(出版商)应该对他们的父母一无所知。它们纯粹通过事件和函数返回值向上通信。然后通过共同的父级路由子级之间的通信(向上的事件,向下的方法调用)。
请注意,由于事件机制的工作方式,您仍然有循环引用(因此要小心处理),但您没有循环依赖关系。子组件甚至可以在完全不同的组件中,没有设计时引用包含父组件的组件。
请注意,您可以对您认为父母的内容和孩子的内容有点灵活。仅仅因为您的主表单创建了一个子表单并不意味着您必须在该方向上定义父子通信。典型的插件风格架构可能具有创建插件实例的主要形式。但是一旦创建,通信可能会将插件视为父/观察者,将主窗体视为发布者/事件源。
只需为 Event 创建一个包含类的项目:
public class BaseFormEventClass
{
public EventHandler<EventArgs> BaseFormDidSomething;
}
然后从 baseform 项目和 components 项目中引用这个项目。让 baseform 创建 eventclass 的实例并将其提供给他加载的所有组件:
public class MyComponent
{
public MyComponent(BaseFormEventClass eventClass)
{
eventClass.BaseFormDidSomething += this.EventClass_BaseFormDidSometing;
}
// ...
}
public class BaseForm
{
private BaseFormEventClass eventClass = new BaseFormEventClass();
private void LoadComponents()
{
MyComponent component1 = new MyComponent(this.eventClass);
}
private void RaiseBaseFormDidSomething()
{
EventHandler<EventArgs> handler = eventClass.BaseFormDidSomething;
if (handler != null) handler(this, EventArgs.Empty);
}
}
您可以使用Microsoft 的 Patterns and Practices Library中的 EventBroker 模式。
但是我不确定这是否是一种很好的模式,我个人更喜欢创建对象不相互引用的架构(始终是父->子场景)并处理依赖问题而不是忽略它们。