How do I avoid the service locator pattern? Should

2020-04-16 04:29发布

I'm currently working on a WinForms system (I know) where there's a lot of Constructor Injection when creating forms, but if those forms/views need to open another form, I find the DI container has been injected too so that we can locate the implementation of the desired view interface at runtime. 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);
    }
}

I'm aware that this is basically using the container as a service locator. I've been repeatedly told that this is considered an anti-pattern so I'd like to avoid this usage.

I could probably inject the view as part of the constructor like 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);
    }
}

But what if the dialog view is quite expensive to instantiate?

It's been suggested that we create some kind of form factory which internally uses the DI container, but to me this seems like simply creating a wrapper around another service locator.

I know that at some point, something has to know how to create an IDialogView, so I'm thinking that either it's resolved when the composite root is created (probably not ideal if there are many forms and some or all are expensive to create), or the composite root itself has a way to resolve the dependency. In which case the composite root has to have a service-locator-like dependency? But then how would child forms create dialogs like this? Would they call up to the composite via, say, events, to open dialogs like this?

One particular problem I keep running up against is that the container is almost impossible to mock easily. This is partly what keeps me thinking about the form factory idea, even though it would just be a wrapper around the container. Is that a sensible reason?

Have I thought myself into a knot? Is there a simple way through this? Or do I just cut the knot and find something that works for me?

4条回答
甜甜的少女心
2楼-- · 2020-04-16 04:49

A local factory, satisfied with an implementation that uses the container and set up in the composition root is not a service locator, it is a dependency resolver.

The difference is as follows: the locator is defined and satisfied somewhere near the definition of the container. In a separate project, to use the locator, you need an external reference to the container infrastructure. This causes the project to rely on external dependency (the locator).

On the other hand, the dependency resolver is local to the project. It is used to satisfy dependencies in its close neighbourhood but it doesn't depend on anything external.

The composition root should not be used to resolve actual specific dependencies such as the one you are concerned about. Instead, the compositon root should set up implementations of all these local dependency resolvers that are used throughout the application. The more local resolver, the better - the MVC's constructor factory is a good example. On the other hand, WebAPI's resolver handles quite few of different services and it is still a good resolver - it is local in the webapi infrastructure, it doesn't depend on anything (rather - other webapi services depend on it) and it can be implemented in any possible way and set up in the Composition Root.

Some time ago I wrote a blog entry about it

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

There you will find your issue discussed and an example of how you set up a factory aka resolver.

查看更多
ゆ 、 Hurt°
3楼-- · 2020-04-16 04:53

You definitely do not want to pass your DI container around your application. Your DI container should only be part of your Composition Root. You could, however, have a factory that uses the DI container within the Composition Root. So if Program.cs is where you are wiring everything up, you could simply define that factory class there.

WinForms was not designed with DI in mind; forms are generated and therefore need default constructors. This may or may not be an issue depending on which DI container you use.

I think in this case, the pain of using constructor injection in WinForms is greater than the pain of any pitfalls you may encounter while using a service locator. There's no shame in declaring a static method in your Composition Root (Program.cs) that wraps a call to your DI container to resolve your references.

查看更多
爷、活的狠高调
4楼-- · 2020-04-16 05:09

Or do I just cut the knot and find something that works for me?

Factory class:

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

Your code:

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

Registration with SimpleInjector

container.RegisterSingle<IDialogFactory, DialogFactory>();

or using singleton version

container.RegisterSingle<IDialogFactory, SingleDialogFactory>();

container.RegisterSingle<IMyView, MyView>();
查看更多
Evening l夕情丶
5楼-- · 2020-04-16 05:10

I know this problem very well. Everything I learned about solution to this (and I learned A LOT) is more or less camouflaging service locator.

查看更多
登录 后发表回答