Внедрение различных классов, реализующих один и тот же интерфейс с использованием 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