简体   繁体   中英

Coupling to Dependency Injection Frameworks

Preface

I feel like there is a real possibility to shoot yourself in the foot when working with DI frameworks.
(My framework of choice is ninject so I'll be using that in my examples.)

I'm going to step back for a second and look at the reason DI frameworks exists:
To prevent having to do DI by hand

Right so, in the spirit of Ninjects documentation, lets say we have a Dojo that creates Samurai s. These Samurai s are given an IWeapon when they are made.

class Samurai{
    readonly IWeapon weapon;

    public Samurai(IWeapon weapon){
        this.weapon = weapon;
    }
}

Now it is my understanding that the Dojo would use Kernel.Get<IWeapon>() when it creates a Samurai .

Woah
Didn't I just couple my Dojo to the Kernel?
Also... How is it supposed to get the Kernel: DI, a singleton, service location?

I feel like we just swiftly defeated the purpose of DI because now I'm dependent on my DI framework. What happens if ninja's are defeated and ninject dies too?

Question

How do we use DI without coupling to a DI framework?

Postface

I'm sure this question has been asked before however I couldn't find anything. Please use the comments to post relevant questions so that we can pool the knowledge to figure out the best solution.

When doing Dependency Injection the trick is to have all your classes inject their dependencies through the constructor. This way you can let the Kernel build up a complete object graph for you from the root object.

This 'root object' however, is something that has to be resolved directly by calling kernel.Get<HomeController>() (if HomeController is the root object). So somewhere in your application you will have to call kernel.Get . It's impossible to go without.

The trick is to minimize this to ideally a single line of code in the application and place this near the startup path. Probably close to the place where you registered your dependencies. The rest of the application stays oblivious to the use of any DI framework.

There are even integration packages for Ninject and other containers that allow you to remove that single line of code depending on which application platform you use. ASP.NET MVC for instance has great extendibility points that allow you to plug in a ControllerFactory that allows you to let that factory call kernel.Get for you.

How do we use DI without coupling to a DI framework?

There will always be some coupling, but ideally this should only be the startup path. All other code should be oblivious to the use of a container. There will still be a assembly dependency, but this dependency will only exist in the startup project, and not in your business layer.

look at the reason DI frameworks exists: To prevent having to do DI by hand

To be more precise. DI helps making your application more maintainable. DI frameworks help making the startup path of your application (aka composition root ) more maintainable.

Actually Service Locator design pattern considered harmful (and actually from many points of view considered as anti-pattern ), so I strongly discourage from using it from your code.

Using Service Locator leads to unclear interface between your class ( class Dojo ) and their clients. Reading your class header would be impossible to understand required context: what should I do to use this class properly: what should I put inside what to fulfill all the requiremenets.

Actually, I prefer using another pattern called Composition Root , when we're using container (aka Service Locator) only in one place in our application: in the root of the application, like Main , HomeController etc.

I strongly suggested to take a look at book on the topic: "Dependency Injection in .NET" by Mark Seeman that covers all this topics in more details.

You could also have some sort of abstraction to your DI container. Consider an interface like this,

public interface IDependencyResolver
{
    T Get<T>();
}

Which you then have an implementation of - for Ninject it could look like this.

public class NinjectDependencyResolver : IDependencyResolver
{
    private readonly IKernel _kernel = new StandardKernel();

    public T Get<T>()
    {
        return _kernel.Get<T>();
    }
}

This means you use Ninject like any other 3rd party component in your project, with the one exception of the line that reads

IDependencyResolver resolver = new NinjectDependencyResolver();

Which should only be found once somewhere, depending on your needs. You then use your IDependencyResolver instance instead of the IKernel provided by Ninject (or similar containers for other frameworks).

To switch DI container is then just a matter of implementing a new IDependencyResolver .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM