Using Ninject with the ServiceLocater Template - Good or Bad

I'm going to get used to Ninject. I understand the principles of Dependency Injection and I know how to use Ninject. But right now I'm a little confused. Opinions drift apart when it comes to the Locator pattern.

My application is strictly modular. I try to use constructor injection as much as possible and it works pretty well, although it's a little messy (in my opinion).

Now that an extension (external code) wants to benefit from this system, would it not be required to have kernel access? I mean, I now have one static class that gives access to all subsystems of my application. An alternative would be to access the kernel (service locator pattern) and grab the subsystem's dependencies from that. This is where I can easily avoid kernel access, or be more explicit by avoiding kernel dependencies.

But if the extension now wants to use any components (interfaces) of my application, from any subsystem, it will need kernel access to enable it, since Ninject will not automatically resolve, re without using "kernel.Get ()", right?

Peww, it's really hard to explain it in an understandable way. Hope you guys get what I'm aiming for.

Why is it so "bad" with kernel or shell? I mean, you cannot avoid all dependencies. For example, I still have my own "Core" class, which gives access to all subsystems. What if an extension wants to register its own module for future use?

I can't find a good answer as to why this is a bad approach, but I've read it quite often. In addition, it is argued that Ninject does NOT take this approach, unlike Unity or similar frameworks.

Thank:)

+3


source to share


2 answers


There are religious wars about this ...

The first thing people say when you mention Service Locator is, "but what if I want to change my container?" This argument is almost always invalid, given that a "correct" service locator can be abstract enough that you can switch the underlying container.

However, using the Locator Service makes it difficult to work with the code in my experience. Your service locator has to be passed all over the place, and then your code is tightly coupled with the very existence of the service locator.

When you use a service locator, you have two main options for supporting modules in "decoupled" mode (little used here ..).

Option 1:



Pass your locator to whatever you need. Essentially, this means that your code will be many, many of this type of code:

var locator = _locator;

var customerService = locator.Get<ICustomerService>();
var orders = customerService.GetOrders(locator, customerId); // locator again

// .. further down..
var repo = locator.Get<ICustomerRepository>();
var orderRepo = locator.Get<IOrderRepository>();

// ...etc...

      

Option 2:

Break all your code into one assembly and find the public static

Service Locator somewhere. This is even worse .. and ends up the same as above (only with direct calls to the Service Locator).

Ninject is lucky (luckily I mean - has a great maintainer / extender in Remo) as it has a bunch of extensions that allow you to take full advantage of Inversion of Control in almost all parts of your system except for the code shown above.

+4


source


This is a bit against SO policy, but to continue with Simon's answer, I'll direct you to a note on Mark Seeman's blog: http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ Some comments on the blog post are very interesting too ...

Now, to solve your problem with ninject and extensions, which I assume you are not aware of / root composition at the time you write them, I would like to specify NinjectModule

s. Ninject already has this kind of extensibility mechanism which is heavily used with / all ninject.extension.XYZ

dlls.

What you will do is implement multiple classes FooExtensionModule : NinjectModule

in your extensions. Modules contain methods Bind

. Now you tell ninject to load all modules from some .dlls. What is it.

More detailed here: https://github.com/ninject/ninject/wiki/Modules-and-the-Kernel

Disadvantages:

  • Extensions depend on Ninject
    • you may need to recompile your extensions when you "update ninject"
    • as the software grows, it will make it all the more expensive to switch DI containers.
  • When using Rebind

    , problems may arise that are difficult to track down (well, this is in the event that you use modules or not).
  • Especially when the developers of an extension are not aware of other extensions, they can create identical or conflicting bindings (for example, .Bind<string>().ToConst("Foo")

    and .Bind<string>().ToConst("Bar")

    ). Again, this is also the case when you are not using Modules, but extensions add another layer of complexity.

Advantage: - Simple and accurate, there is no extra layer of complexity / abstraction that you need to distract the container.

I have used the approach NinjectModule

in a small application (15kbps / component tests) with great success.

If you want simple bindings like .Bind<IFoo>().To<Foo>()

no scopes, etc., you might also consider using a simpler system, like putting attributes on classes, scanning them, and creating bindings at the root of the composition. This approach is less efficient, but because of this it is much more "portable" (adaptable for use with another DI container) too.

Late Instance Dependency Injection



the idea behind composition root is that (if possible) you create all the objects (the whole object graph) in one go. For example, in a method Main

, you might have kernel.Get<IMainViewModel>().Show()

.

However, sometimes this is not possible or advisable. In such cases, you will need to use factories. There are actually a bunch of answers already on the way to stackoverflow. There are three main types:

  • To create an instance Foo

    that requires instances Dependency1

    and Dependency2

    (ctor injected), create a class FooFactory

    that receives one instance Dependency1

    and a Dependency2

    ctor. The method FooFactory.Create

    will then execute new Foo(this.dependency1, this.dependency2)

    .
  • use ninject.extensions.Factory :
    • use Func<Foo>

      like a factory: you can inject Func<Foo>

      and then call it to create an instance Foo

      .
    • Use interfaces and bind .ToFactory()

      (I recommend this approach. Cleaner code, better validation). For example: IFooFactory

      using the method Foo Create()

      . Bind it like this:Bind<IFooFactory>().ToFactory();

Extensions that replace implementations

IMHO this is not one of the goals of dependency injection containers. This does not mean that it is impossible, but it simply means that you have to figure it out for yourself.

The simplest thing you could with ninject would be to use .Rebind<IFoo>().To<SomeExtensionsFoo>()

. However, as stated earlier, this is a bit fragile. If Bind

u Rebind

are executed in the wrong sequence, it fails. If there are multiple Rebind

s, the latter will win - but is it correct?

So let's take it one step further. Imagine:

`.Bind<IFoo>().To<SomeExtensionsFoo>().WhenExtensionEnabled<SomeExtension>();`

      

you can develop your own extension method WhenExtensionEnabled<TExtension>()

that extends the syntax method When(Func<bool> condition)

. You will need to develop a way to determine if an extension is enabled or not.

+2


source







All Articles