如何避免服务定位器模式?我是不是该?

问题描述 投票:1回答:4

我正在开发一个WinForms系统(我知道),在创建表单时有很多Constructor Injection,但如果这些表单/视图需要打开另一个表单,我发现DI容器已被注入,以便我们可以找到在运行时实现所需的视图接口。例如

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);
    }
}

我知道这基本上是使用容器作为服务定位器。我一再被告知这被认为是一种反模式,所以我想避免这种用法。

我可以将视图作为构造函数的一部分注入,如下所示:

public partial class MyView : Form, IMyView
{
    private readonly IDialogView _dialog;

    public MyView(IDialogView dialog)
    {
        InitializeComponent();

        _dialog = dialog;
    }

    public OpenDialogClick(object sender, EventArgs e)
    {
        dialog.ShowDialog(this);
    }
}

但是,如果对话框视图实例化非常昂贵呢?

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

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

我一直遇到的一个特殊问题是容器几乎不可能轻易地模拟。这部分是让我思考表单工厂理念的部分原因,即使它只是容器的包装。这是一个明智的理由吗?

我是否认为自己陷入了困境?有一个简单的方法吗?或者我只是剪结并找到适合我的东西?

c# winforms dependency-injection inversion-of-control ioc-container
4个回答
1
投票

或者我只是剪结并找到适合我的东西?

工厂类:

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;
   }
}

你的代码:

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);
      }
   }
}

注册SimpleInjector

container.RegisterSingle<IDialogFactory, DialogFactory>();

或使用单身版本

container.RegisterSingle<IDialogFactory, SingleDialogFactory>();

container.RegisterSingle<IMyView, MyView>();

1
投票

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

不同之处如下:定位器在容器定义附近的某处定义和满足。在单独的项目中,要使用定位器,您需要对容器基础结构的外部引用。这导致项目依赖于外部依赖(定位器)。

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

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

前段时间我写了一篇关于它的博客文章

http://www.wiktorzychla.com/2012/12/di-factories-and-composition-root.html

在那里,您将找到您讨论的问题以及如何设置工厂(即解析器)的示例。


0
投票

您绝对不希望在您的应用程序周围传递DI容器。您的DI容器应该只是Composition Root的一部分。但是,您可以在合成根中使用DI容器。因此,如果Program.cs是您连接所有内容的地方,那么您可以在那里定义该工厂类。

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

我认为在这种情况下,在WinForms中使用构造函数注入的痛苦大于使用服务定位器时可能遇到的任何陷阱的痛苦。在Composition Root(Program.cs)中声明一个静态方法,包含对DI容器的调用来解析引用,这并不遗憾。


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.