Внедрение различных классов, реализующих один и тот же интерфейс с использованием Ninject
Я реализую шаблон проектирования построителя для построения различных видов объектов графа, которые будут отображаться в интерфейсе WPF. Я использую Ninject как контейнер IOC. Тем не менее, я пытаюсь найти элегантное расширяемое решение.
У меня есть объект ChartDirector
, который принимает IChartBuilder
как зависимость. У меня также есть TemperatureChartBuilder
и ThresholdChartBuilder
, которые реализуют IChartBuilder
. Я хочу добавить либо TemperatureChartBuilder
OR ThresholdChartBuilder
в ChartDirector
в зависимости от события, которое уволено, либо в зависимости от клиентского вызова. Я проиллюстрировал свою проблему ниже в коде.
// ChartDirector also depends on this
kernel.Bind<IExample>().To<Example>();
// when called in Method X...
kernel.Bind<IChartBuilder>().To<TemperatureChartBuilder>();
// when called in Method Y...
kernel.Bind<IChartBuilder>().To<ThresholdChartBuilder();
// TemperatureChartBuilder is a dependency of ChartDirector, need a way to dynamically
// allocate which binding to use.
var director = kernel.Get<ChartDirector>();
// without Ninject I would do
var director = new ChartDirector(new TemperatureChartBuilder);
// or
var director = new ChartDirector(new ThresholdChartBuilder);
EDIT:
В сочетании с ответом Гэри и отмечая небольшое изменение, что ChartDirector имеет другую зависимость, теперь я хочу сделать что-то вроде этого:
var director = kernel.Get<ChartDirector>().WithConstructorArgument(kernel.Get<IChartBuilder>("TemperatureChart"));
Возможно ли подобное?
Ответы
Ответ 1
Если вы планируете использовать местоположение службы, как и в ваших примерах, то имена привязок отлично работают, как говорит Гари.
Однако лучшим подходом является использование инъекции конструктора и использование атрибутов. Например, из вики файла ninject:
Bind<IWeapon>().To<Shuriken>().Named("Strong");
Bind<IWeapon>().To<Dagger>().Named("Weak");
...
class WeakAttack {
readonly IWeapon _weapon;
public([Named("Weak")] IWeapon weakWeapon)
_weapon = weakWeapon;
}
public void Attack(string victim){
Console.WriteLine(_weapon.Hit(victim));
}
}
Основываясь на вашем комментарии к Гэри, вы (как ни странно) натыкаетесь на территорию, подобную той, о которой я задал вопрос несколько часов назад. См. Ответ Remo здесь: Использование WithConstructorArgument и создание связанного типа
Вы должны использовать условие When, чтобы определить, когда создавать правильный экземпляр.
Ответ 2
Я бы предложил использовать контекстные привязки (называемые привязками специально) для этого. Таким образом вы можете сделать что-то вроде:
// called on app init
kernel.Bind<IChartBuilder>().To<TemperatureChartBuilder>().Named("TempChartBuilder");
kernel.Bind<IChartBuilder>().To<ThresholdChartBuilder().Named("ThreshChartBuilder");
// method X/Y could both call method Z that grabs the correct chart director
var director = new ChartDirector(kernel.Get<IChartBuilder>("TempChartBuilder"));
Где "TempChartBuilder" может быть переменной, которая сообщает ninject, который привязывает к разрешению. Таким образом, довольно привязанный на лету вы разрешаете "на лету", но все привязки могут быть определены спереди. Обычно контейнеры IOC хранятся на уровне домена приложения и должны быть определены только один раз. Могут быть конкретные случаи, когда вам нужно связывать динамически, но они должны быть редкими.
Дополнительная информация о контекстных привязках: https://github.com/ninject/ninject/wiki/Contextual-Binding