抽象是好的,但重要的是要记住,在某些时候,必须对一两件事了解一两件事,否则我们只会把一堆抽象得很好的乐高积木放在地板上,而不是把它们组装成一个房子。
像Autofac这样的控制反转/依赖注入/flippy-dippy-upside-down-whatever-were-call-it-this-week 容器可以真正帮助将这一切拼凑在一起。
当我将一个 WinForms 应用程序放在一起时,我通常会得到一个重复的模式。
我将从一个Program.cs
配置 Autofac 容器的文件开始,然后从中获取一个实例MainForm
,并显示MainForm
. 有些人称其为外壳或工作区或桌面,但无论如何它是具有菜单栏并显示子窗口或子用户控件的“表单”,当它关闭时,应用程序退出。
接下来是前面提到的MainForm
。我在 Visual Studio 视觉设计器中做一些基本的事情,比如拖放一些SplitContainers
和MenuBar
s 等,然后我开始对代码感兴趣:我将某些关键接口“注入”到MainForm
's 的构造函数中,这样我可以利用它们,这样我的 MainForm 就可以编排子控件,而不必真正了解它们。
例如,我可能有一个IEventBroker
接口,可以让各种组件发布或订阅“事件”,如BarcodeScanned
or ProductSaved
。这允许应用程序的某些部分以松散耦合的方式响应事件,而不必依赖于连接传统的 .NET 事件。例如,EditProductPresenter
我EditProductUserControl
可以说this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah))
和IEventBroker
会检查它的订阅者列表以查找该事件并调用他们的回调。例如,ListProductsPresenter
可以监听该事件并动态更新ListProductsUserControl
它所附着的。最终结果是,如果用户将产品保存在一个用户控件中,另一个用户控件的演示者可以在它碰巧打开时做出反应并自行更新,而任何一个控件都不必知道彼此的存在,也不必MainForm
进行协调那个事件。
如果我正在设计一个 MDI 应用程序,我可能会MainForm
实现一个IWindowWorkspace
具有Open()
和Close()
方法的接口。我可以将该界面注入到我的各种演示者中,以允许他们打开和关闭其他窗口,而他们不会MainForm
直接意识到这一点。例如,ListProductsPresenter
当EditProductPresenter
用户EditProductUserControl
双击ListProductsUserControl
. 它可以引用一个-- 这IWindowWorkspace
实际上是MainForm
,但它不需要知道这一点 -- 并调用Open(newInstanceOfAnEditControl)
并假设控件以某种方式显示在应用程序的适当位置。(MainForm
据推测,该实现会将控件交换到某个面板上的视图中。)
但是到底是如何ListProductsPresenter
创建的那个实例EditProductUserControl
呢?Autofac 的委托工厂在这里真的很有趣,因为您只需将委托注入到演示者中,Autofac 就会自动将其连接起来,就好像它是工厂一样(伪代码如下):
public class EditProductUserControl : UserControl
{
public EditProductUserControl(EditProductPresenter presenter)
{
// initialize databindings based on properties of the presenter
}
}
public class EditProductPresenter
{
// Autofac will do some magic when it sees this injected anywhere
public delegate EditProductPresenter Factory(int productId);
public EditProductPresenter(
ISession session, // The NHibernate session reference
IEventBroker eventBroker,
int productId) // An optional product identifier
{
// do stuff....
}
public void Save()
{
// do stuff...
this.eventBroker.Publish("ProductSaved", new EventArgs(this.product));
}
}
public class ListProductsPresenter
{
private IEventBroker eventBroker;
private EditProductsPresenter.Factory factory;
private IWindowWorkspace workspace;
public ListProductsPresenter(
IEventBroker eventBroker,
EditProductsPresenter.Factory factory,
IWindowWorkspace workspace)
{
this.eventBroker = eventBroker;
this.factory = factory;
this.workspace = workspace;
this.eventBroker.Subscribe("ProductSaved", this.WhenProductSaved);
}
public void WhenDataGridRowDoubleClicked(int productId)
{
var editPresenter = this.factory(productId);
var editControl = new EditProductUserControl(editPresenter);
this.workspace.Open(editControl);
}
public void WhenProductSaved(object sender, EventArgs e)
{
// refresh the data grid, etc.
}
}
所以ListProductsPresenter
了解Edit
功能集(即编辑演示者和编辑用户控件)——这很好,它们齐头并进——但它不需要知道所有的依赖关系Edit
功能集,而不是依靠 Autofac 提供的委托来解决它的所有这些依赖关系。
一般来说,我发现我在“演示者/视图模型/监督控制器”之间存在一对一的对应关系(让我们不要太关注差异,因为在一天结束时它们都非常相似)和“ UserControl
/ Form
"。在其UserControl
构造函数中接受演示者/视图模型/控制器,并在适当的时候对自身进行数据绑定,尽可能地服从演示者。UserControl
有些人通过界面(例如 )向演示者隐藏IEditProductView
,如果视图不是完全被动的,这可能很有用。我倾向于对所有事情都使用数据绑定,这样通信就可以通过INotifyPropertyChanged
并且不用打扰。
但是,如果演示者无耻地与视图挂钩,您的生活会变得更加轻松。您的对象模型中的属性是否与数据绑定不匹配?暴露一个新的属性。你永远不会有一个EditProductPresenter
和EditProductUserControl
一个布局,然后想要编写一个与同一个演示者一起工作的新版本的用户控件。您将只编辑它们,它们用于所有意图和目的一个单元,一个功能,演示者仅存在,因为它易于单元测试而用户控件不是。
如果你想要一个特性是可替换的,你需要抽象整个特性。因此,您可能有一个与之对话的INavigationFeature
界面。MainForm
您可以拥有一个TreeBasedNavigationPresenter
实现INavigationFeature
并被 a 使用的 a TreeBasedUserControl
。而且您可能有一个CarouselBasedNavigationPresenter
也实现INavigationFeature
并被 a 使用的 a CarouselBasedUserControl
。用户控件和演示者仍然齐头并进,但您MainForm
不必关心它是与基于树的视图还是基于轮播的视图交互,您可以在不明智的情况下将它们换掉MainForm
。
最后,很容易混淆自己。每个人都是迂腐的,并且使用稍微不同的术语来传达他们在相似架构模式之间的微妙(而且通常是不重要的)差异。在我看来,依赖注入对于构建可组合、可扩展的应用程序确实有很大的帮助,因为耦合被抑制了。将功能分离为“演示者/视图模型/控制器”和“视图/用户控件/表单”对质量有好处,因为大多数逻辑都被引入前者,从而可以轻松进行单元测试;并且将这两个原则结合起来似乎确实是您正在寻找的东西,您只是对术语感到困惑。
或者,我可以充满它。祝你好运!