Search code examples
c#dependency-injectioninversion-of-controlninject

One samurai with a sword and one with a dagger


Thanks for looking. I'm kind of new to Ninject and like it so far. I get the part where you bind one thing in debug mode and bind another in release mode. Those are global bindings where you have to declare that every Samurai will have a sword or a dagger, using Ninjects example code. It's not either/or, it's one or the other.

How do I do something where I can have one Samurai with a sword, and another with a dagger where they could even switch weapons if they like. Is there another way other than creating a bunch of kernels with different binding modules?

Here is the example code from Ninject. If you drop it in a console app it should run:

using System;
using Ninject;

namespace NinjectConsole
{
    class Program
    {

        //here is where we have to choose which weapon ever samurai must use...
        public class BindModule : Ninject.Modules.NinjectModule
        {
            public override void Load()
            {
                //Bind<IWeapon>().To<Sword>();
                Bind<IWeapon>().To<Shuriken>();
            }
        }

        class Shuriken : IWeapon
        {
            public void Hit(string target)
            {
                Console.WriteLine("Pierced {0}'s armor", target);
            }
        }

        class Sword : IWeapon
        {
            public void Hit(string target)
            {
                Console.WriteLine("Chopped {0} clean in half", target);
            }
        }

        interface IWeapon
        {
            void Hit(string target);
        }

        class Samurai
        {
            readonly IWeapon weapon;

            [Inject]
            public Samurai(IWeapon weapon)
            {
                if (weapon == null)
                    throw new ArgumentNullException("weapon");

                this.weapon = weapon;
            }

            public void Attack(string target)
            {
                this.weapon.Hit(target);
            }
        }

        static void Main(string[] args)
        {

            //here is where we bind...
            Ninject.IKernel kernel = new StandardKernel(new BindModule());

            var samurai = kernel.Get<Samurai>();
            samurai.Attack("your enemy");

            //here is I would like to do, but with DI and no local newing up...
            var warrior1 = new Samurai(new Shuriken());
            var warrior2 = new Samurai(new Sword());
            warrior1.Attack("the evildoers");
            warrior2.Attack("the evildoers");
            Console.ReadKey();
        }
    }
}

EDIT

Thanks for the replays and suggestions.

I figured out how to get pretty much what I wanted. Okay, so here is what I did:

  1. Set the default/initial binding to the weakest weapon. Sort of like a new-b would do.
  2. Added another weapon (Dagger)
  3. Expanded IWeapon to include a WeaponHitPoints value to rate the weapons value.
  4. Expanded Samurai to include an add and drop weapon method so the Samurai can gain or lose weapons.
  5. Modified the Attack method to use the best weapon.
  6. Modified the program to make use of the added features.
  7. TODO: add try/catch and null checks...

Create a Console project called NinjectConsole, install Ninject and you should be able to just drop this in and run it.

Here is the new code:

using System;
using System.Collections.Generic;
using System.Linq;
using Ninject;

namespace NinjectConsole
{
    class Program
    {
        public class BindModule : Ninject.Modules.NinjectModule
        {
            // default bind to weakest weapon 
            public override void Load()
            {
                Bind<IWeapon>().To<Dagger>();
            }
        }

        class Dagger : IWeapon
        {
            public int WeaponHitPoints { get { return 5; } }
            public string Hit(string target)
            {
                return String.Format("Stab {0} to death", target);
            }
        }

        class Shuriken : IWeapon
        {
            public int WeaponHitPoints { get { return 9; } }

            public string Hit(string target)
            {
                return String.Format("Pierced {0}'s armor", target);
            }
        }

        class Sword : IWeapon
        {
            public int WeaponHitPoints { get { return 11; } }

            public string Hit(string target)
            {
                return string.Format("Chopped {0} clean in half", target);
            }
        }

        interface IWeapon
        {
            int WeaponHitPoints { get; }
            string Hit(string target);
        }

        private class Samurai
        {
            private IEnumerable<IWeapon> _allWeapons;

            public Samurai(IWeapon[] allWeapons)
            {
                if (!allWeapons.Any())
                    throw new ArgumentException("Samurai");

                _allWeapons = allWeapons;
            }

            public void AddWeapon(IWeapon weapon)
            {  //TODO: check for nulls...
                _allWeapons = _allWeapons.Concat(new[] { weapon });
            }

            public void DropWeapon(IWeapon weapon)
            {  //TODO: check for nulls...
                Console.WriteLine("A Samurai got rid of a " + weapon.WeaponName);

                _allWeapons = _allWeapons.Where(x => x.WeaponName != weapon.WeaponName);
            }

            public void Attack(string target)
            {
                int points = 0;

                try
                {
                    points = _allWeapons.Max(x => x.WeaponHitPoints);
                }
                catch ()
                {
                    Console.WriteLine("You just punched " + target + " on the nose!");
                }

                var attackWeapon = _allWeapons.FirstOrDefault(i => i.WeaponHitPoints == points);

                //TODO: check for nulls... 
                Console.WriteLine(attackWeapon.Hit(target));
            }
        }

        static void Main(string[] args)
        {
            Ninject.IKernel kernel = new StandardKernel(new BindModule());

            var samurai1 = kernel.Get<Samurai>();
            var samurai2 = kernel.Get<Samurai>();

            Console.WriteLine("Samurai #1");
            samurai1.Attack("your enemy");

            samurai2.AddWeapon(new Shuriken());

            Console.WriteLine("\nSamurai #2 selects best weapon for attack");
            samurai2.Attack("your enemy");

            Console.WriteLine("\nSamurai #1 gets new weapon!");
            samurai1.AddWeapon(new Sword());

            Console.WriteLine("Samurai #1 selects best weapon for attack");
            samurai1.Attack("your enemy");

            Console.ReadKey();
        }
    }
}

Solution

  • Generally speaking, you can't achieve this with an IOC container unless you specify some condition, which should be met to choose the right implementation (weapon). The container needs to know which one implementation to choose under current circumstances.

    I suggest, you are looking for some sort of Contextual binding.

    There are plenty of conditional binding methods in Ninject (see them all on the link above). I have chosen Named binding, as it is very straightforward for an example.

    Named binding

    Dependencies are resolved according to configured names.

    kernel.Bind<Samurai>().ToSelf().Named("SwordMaster");
    kernel.Bind<Samurai>().ToSelf().Named("ShurikenMaster");
    
    kernel.Bind<IWeapon>().To<Sword>().WhenParentNamed("SwordMaster");
    kernel.Bind<IWeapon>().To<Shuriken>().WhenParentNamed("ShurikenMaster");
    
    warrior1 = kernel.Get<Samurai>("SwordMaster");
    warrior2 = kernel.Get<Samurai>("ShurikenMaster");
    

    Multi injection

    If you want your Samurai to be able to handle multiple weapons, you can declare multiple bindings for IWeapon and those could be injected into Samurai as a collection.

    public Samurai(IEnumerable<IWeapon> weapons)
    {
         this.AllMyWeapons = weapons;
    }