开发者

Still need help understanding why Ninject might be better than manual DI

开发者 https://www.devze.com 2023-02-22 16:52 出处:网络
This is an extension to the question Why do I need an IoC container as opposed to straightforward DI code?

This is an extension to the question Why do I need an IoC container as opposed to straightforward DI code?

I've been learning Ninject and came up with the following example, the example goes through the manual way of doing DI and the Ninject way of doing DI:

class Program
{
    static void Main(string[] args)
    {
        NinjectWay();
        ManualWay();
        Console.ReadKey();
    }

    private static void ManualWay()
    {
        Console.WriteLine("ManualWay***********************");
        IWeapon sword = new Sword();
        Samurai samurai = new Samurai(sword);

        Console.WriteLine(samurai.Attack("ManualWay..."));

        // change weapon
        IWeapon dagger = new Dagger();
        samurai.Weapon = dagger;

        Console.WriteLine(samurai.Attack("ManualWay..."));

        IWeapon weapon = new Shuriken();
        IWarrior ninja = new Ninja(weapon);
        Console.WriteLine("Manual way.. inject shuriken when a ninja. " + ninja.Weapon.Name);

        IWarrior ninja2 = new Ninja(weapon);
    }

    private static void NinjectWay()
    {
        Console.WriteLine("NinjectWay***********************");
        IKernel kernel = new StandardKernel();
        kernel.Bind<IWeapon>().To<Sword>();
        var samurai = kernel.Get<Samurai>();

        Console.WriteLine(samurai.Attack("NinjectWay..."));

        kernel.Rebind<IWeapon>().To<Dagger>();
        samurai = kernel.Get<Samurai>();

        Console.WriteLine(samurai.Attack("NinjectWay..."));

        kernel.Bind<IWeapon>().To<Shuriken>().WhenInject开发者_Python百科edInto<Ninja>();
        var ninja = kernel.Get<Ninja>();
        ninja.OffHandWeapon = new ShortSword();

        Console.WriteLine("Conditional Injection..."+ninja.Weapon.Name);
        Console.WriteLine("Conditional Injection: OffhandWeapon = " + ninja.OffHandWeapon.Name);

        var ninja2 = kernel.Get<Ninja>();
        Console.WriteLine("Conditional Injection..." + ninja2.Weapon.Name);
        Console.WriteLine("Conditional Injection: OffhandWeapon = " + ninja2.OffHandWeapon.Name);

        Console.WriteLine("");
    }
}

I hear the benefits happen when the scale of the project increases but I'm not seeing it. Help me understand this better. Provide more examples in C#/Ninject and help me understand where the benefits really become apparent.


Unlike the other answers suggest Ninject isn't mainly about making your code more testable. It's Dependency Injection that makes your code more testable! Dependency Injection can be used without an IoC container by creating everything in facotries. But sure, beeing able to replace some parts easily for Integration Tests (don't use Ninject in unit tests) is a nice side effect.

IoC containers like Ninject are mainly about putting your classes together to a working software. In small projects this can easily be done using some factories. But as your application grows the factories get more and more complicated. Imagine an application that has various services some of them are reused others are newly created for every usage. Some of the services are also used by several components.

If you are using a IoC container you have to define exactly once how you get the service instance and what's its lifecycle. On the other hand in a factory you have to specify how you get the instance for every class that needs an instance (e.g. new MyServiceFactory().CreateInstance()). Furthermore, you have to control the lifecycle manually.

This means as the project grows the configuration of an IoC container grows linear together with the project size. But a factory on the other hand grows more exponentional like as there are services that are used throughout the application (e.g. user auth).

BTW: Your example isn't very good because Rebinding isn't a common action. Usually the configuration is done only once at application startup.


GREAT QUESTION!

The BIG BIG win IMHO is when your code that asks for instantiations doesn't know or care what the actual implementations are. This is most evident in mocking - your mocking framework can configure Ninject to return mocked Ninja's, Sword's, and Samarai's that implement behavior your expect in completely different ways.

I have a repositry layer, for example, that depends on IoC to get access to a data-store. For testing, that data-store is a hand-constructed collection of objects. For remote access, the data-store consumes web services. Locally, it's SQL. The repository simply asks for the IDataStore from the IoC and gets whatever was configured to serve up.

Does that help?


One benefit comes in testing.

You can bind Sword to IWeapon in your production code, and FakeSword to IWeapon in your test assembly. This is useful when you need to test something that has a dependency on IWeapon but you don't actually want a real sword.

For example, instead of Sword and IWeapon you have IDataContext and DataContext. Great in your production code when you need to connect to a DB. But for unit tests you probably don't want to actually hit the database for a variety of reasons (performance, inconsistent data etc.) So you'd wire up a FakeDataContext to your IDataContext. Now you've got some control to simulate DB activity for testing.


The main advantage Ninject has over you doing it manually is that somebody else has done a lot of the abstracting for you. Clearly you could reproduce a similar thing by writing similar code to Ninject (something for creating the objects based around interface types), which would allow you to separate your object construction from your object logic but Ninject (and other libraries) have all ready done a lot of the hard work for you so unless you're going to add something new why would you want to create it all again?

The comparison you've got in your question isn't really representative of the way I'd expect Ninject to be used. You're binding and rebinding in the same method that's using the objects. I'd expect the container setup to be done somewhere else. This allows you to change the construction (so that for example it's easier to test by creating mock objects) without altering the code that actually uses the constructed object.

0

精彩评论

暂无评论...
验证码 换一张
取 消