IoC / DI containers, factories and runtime type creation
我最近了解了DI框架Guice和Ninject,并希望在我的一些新项目中使用它们。
虽然我熟悉一般的依赖注入概念,并且知道如何使用这些框架来构建对象图,但是在涉及动态应用程序行为时,我却很难应用IoC。
请考虑以下示例:
- 当应用程序启动时,将显示主窗口。
- 当用户单击主面板时,将打开一个上下文菜单。
- 根据用户的选择,将创建一个新的用户控件并将其显示在鼠标位置。
- 如果用户最终决定关闭该应用程序,将显示一个确认框,并且在确认后将关闭主窗口。
虽然很容易将主窗口的View连接到Presenter / ViewModel,然后将其绑定到域逻辑,但我不明白如何干净地(就IoC而言)完成以下任务:
-
动态实例化具体的UI控件(例如
IGreenBoxView ,IRedImageView <-JConcreteGreenBoxView ,JConcreteRedImageView ),而无需使用任何类型的服务定位器模式(例如再次请求IoC)- 以此为基础,创建一个新的模型,演示者并查看实例
-
类似地,实例化一个新的具体对话框,例如
JOptionPane 在运行时
我已经看到了一些使用抽象工厂的解决方案,但是老实说,他们并没有完全理解它们。看来,这样的解决方案将导致某些(视图域,演示者域等)内部类型暴露给构造根,进而暴露给整个世界。
所以-我该怎么做?
如果可以重用控件,则可以在使用控件的地方进行构造函数注入。否则,您必须注入工厂:
1 2 3 4 5 | public interface IControlFactory { IGreenBoxView CreateGreenBoxView(); IRedImageView CreateRedImageView(); } |
并将其注入需要创建此控件的位置。
实现转到容器配置。在那里,您可以将容器注入到实现中。一些容器提供了自动实现该工厂的功能。例如,在Ninject中:
1 | Bind<IControlFactory>().ToFactory(); |
请参阅https://github.com/ninject/ninject.extensions.factory/wiki
对于那些希望使用Unity进行类似操作(而不是Ninject)的人,我创建了一个扩展,该扩展使您无需声明接口即可创建工厂:UnityMappingFactory @ GitHub
您只需在正常的自举过程中将映射添加到您注册类的位置即可...
1 2 3 4 5 6 7 8 | //make sure to register the output... container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>(); container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>(); //define the mapping between different class hierarchies... container.RegisterFactory<IWidget, IWidgetViewModel>() .AddMap<IImageWidget, IImageWidgetViewModel>() .AddMap<ITextWidget, ITextWidgetViewModel>(); |
然后,您只需在CI的构造函数中声明映射工厂接口,并使用其Create()方法...
1 2 3 4 5 6 7 8 9 10 | public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { } public TextWidgetViewModel(ITextWidget widget) { } public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory) { IList<IWidgetViewModel> children = new List<IWidgetViewModel>(); foreach (IWidget w in data.Widgets) children.Add(factory.Create(w)); } |
作为额外的好处,在创建对象的过程中,映射类的构造函数中的任何其他依赖关系也将得到解决。 (也分享了此Stackoverflow帖子的答案)