C# 如何避免服务定位器模式?我应该吗?

C# 如何避免服务定位器模式?我应该吗?,c#,winforms,dependency-injection,inversion-of-control,ioc-container,C#,Winforms,Dependency Injection,Inversion Of Control,Ioc Container,我目前正在开发一个WinForms系统(我知道),其中在创建表单时有很多构造函数注入,但是如果这些表单/视图需要打开另一个表单,我发现DI容器也被注入了,这样我们就可以在运行时找到所需视图接口的实现。e、 g public partial class MyView : Form, IMyView { private readonly IDIContainer _container; public MyView(IDIContainer container) {

我目前正在开发一个WinForms系统(我知道),其中在创建表单时有很多
构造函数注入
,但是如果这些表单/视图需要打开另一个表单,我发现DI容器也被注入了,这样我们就可以在运行时找到所需视图接口的实现。e、 g

public partial class MyView : Form, IMyView
{
    private readonly IDIContainer _container;

    public MyView(IDIContainer container)
    {
        InitializeComponent();

        _container = container;
    }

    public OpenDialogClick(object sender, EventArgs e)
    {
        var dialog = container.Resolve<IDialogView>();
        dialog.ShowDialog(this);
    }
}
但是,如果对话框视图的实例化成本很高怎么办

有人建议我们创建某种在内部使用DI容器的表单工厂,但对我来说,这似乎只是在另一个服务定位器周围创建一个包装器

我知道在某个时候,有些东西必须知道如何创建IDialogView,所以我认为要么在创建复合根时解决它(如果有许多表单,并且一些或所有表单的创建成本都很高,可能并不理想),要么复合根本身有解决依赖关系的方法。在这种情况下,复合根必须具有类似服务定位器的依赖关系?但是,子窗体如何创建这样的对话框呢?他们会通过,比如说,事件,调用合成来打开这样的对话框吗

我经常遇到的一个特殊问题是,容器几乎不可能轻松模拟。这是让我一直思考FormFactory想法的部分原因,即使它只是容器的包装。这是合理的理由吗


我是否觉得自己陷入了困境?有没有一个简单的方法?或者我只是想解决问题,找到适合我的东西?

一家本地工厂,对使用容器并在组合根目录中设置的实现感到满意,它不是一个服务定位器,而是一个依赖项解析程序

区别如下:定位器是在容器定义附近的某个地方定义和满足的。在单独的项目中,要使用定位器,需要对容器基础结构进行外部引用。这导致项目依赖于外部依赖项(定位器)

另一方面,依赖项解析器是项目的本地解析器。它用于满足邻近区域的依赖关系,但不依赖于任何外部因素

组合根不应用于解决实际的特定依赖项,例如您所关心的依赖项。相反,复合根目录应该设置在整个应用程序中使用的所有这些本地依赖项解析程序的实现。本地解析器越多越好-MVC的构造函数工厂就是一个很好的例子。另一方面,WebAPI的解析器可以处理相当多的不同服务,它仍然是一个很好的解析器——它在WebAPI基础设施中是本地的,不依赖于任何东西(而是其他WebAPI服务依赖于它),并且可以以任何可能的方式实现并在组合根目录中设置

不久前我写了一篇关于它的博客

在那里,您将发现您的问题得到了讨论,并且是一个如何设置工厂(也称为解析器)的示例

还是我只是开门见山,找到适合我的东西

工厂等级:

public interface IDialogFactory { IDialogView CreateNew(); } // Implementation sealed class DialogFactory: IDialogFactory { public IDialogView CreateNew() { return new DialogImpl(); } } // or singleton... sealed class SingleDialogFactory: IDialogFactory { private IDialogView dialog; public IDialogView CreateNew() { if (dialog == null) { dialog = new DialogImpl(); } return dialog; } } 公共接口IDialogFactory{ IDialogView CreateNew(); } //实施 密封类对话框工厂:IDialogFactory{ 公共IDialogView CreateNew(){ 返回新的DialogImpl(); } } //或者单身。。。 密封类SingleDialogFactory:IDialogFactory{ 私有IDialogView对话框; 公共IDialogView CreateNew(){ 如果(对话框==null){ dialog=newdialogimpl(); } 返回对话框; } } 您的代码:

public partial class MyView : Form, IMyView { private readonly IDialogFactory factory; public MyView(IDialogFactory factory) { InitializeComponent(); //assert(factory != null); this.factory = factory; } public OpenDialogClick(object sender, EventArgs e) { using (var dialog = this.factory.CreateNew()) { dialog.ShowDialog(this); } } } 公共部分类MyView:Form,IMyView{ 私人只读IDialogFactory工厂; 公共MyView(IDialogFactory工厂){ 初始化组件(); //断言(工厂!=null); 这个工厂=工厂; } 公共OpenDialogClick(对象发送者,事件参数e){ 使用(var dialog=this.factory.CreateNew()){ dialog.ShowDialog(这个); } } } 注册

container.RegisterSingle();
或者使用单例版本

container.RegisterSingle<IDialogFactory, SingleDialogFactory>();

container.RegisterSingle<IMyView, MyView>();
container.RegisterSingle();
container.RegisterSingle();

您肯定不想在应用程序中传递DI容器。您的DI容器应该只是您的组合根的一部分。但是,您可以有一个工厂,在组合根目录中使用DI容器。所以,如果Program.cs是连接所有内容的地方,那么可以在那里简单地定义工厂类

WinForms的设计没有考虑到DI;表单是生成的,因此需要默认构造函数。这可能是问题,也可能不是问题,这取决于您使用的DI容器


我认为在这种情况下,在WinForms中使用构造函数注入的痛苦比使用服务定位器时可能遇到的任何陷阱都要大。在组合根目录(Program.cs)中声明一个静态方法并不羞耻,它封装了对DI容器的调用以解决引用问题。

我非常了解这个问题。我所学到的关于这个解决方案的一切(我学到了很多)或多或少都是伪装服务定位器。

我想这不会带他去任何地方。现在,要创建
MyView
的实例,他仍然需要容器来解决依赖关系。相反,工厂应该是一个具有可插入提供程序的具体类。
MyView
应该直接使用工厂,不需要注入工厂。工厂提供程序是在合成根目录中设置的。很抱歉,但我不明白为什么“要创建MyView实例,他仍然需要容器来解决依赖关系”。Qithout使用我可以编写的容器:var fac=new SingleFactory();var myView=新myView(fac);这与根据接口注册实现的想法背道而驰。客户如何知道
IDialogFactory
已注册到
SingleFactorycontainer.RegisterSingle<IDialogFactory, DialogFactory>();
container.RegisterSingle<IDialogFactory, SingleDialogFactory>();

container.RegisterSingle<IMyView, MyView>();