问题描述:

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

}

}

}

网友答案:

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;
}
网友答案:

Unfortunately although the Ninject documentation makes it easy to understand the syntax of the container, it can also give the wrong impression of when you should be using an IoC container.

The idea is that your services are registered and resolved from the IoC container - but a Samurai isn't really a service, it's a domain object. These should be constructed by (for example) a SamuraiFactory (Now there's a scary thought...)

When resolving a service, it is expected that you will only need to inject your components once, during initialization. This is the only time when the IoC container is used - ideally you should call .Resolve() just once, in order to spin up your network of dependencies. When you have your composition root, you have your entry point to the program - from then on, you do not reference the IoC container and your program executes as normal.

相关阅读:
Top