Ninject Bind, когда предок типа T

У меня есть цепочка зависимостей, которая выглядит примерно так:

public class CarSalesBatchJob
{
    public CarSalesBatchJob(IFileProvider fileProvider)
    { ... }
}

public class MotorcycleSalesBatchJob
{
    public MotorcycleSalesBatchJob(IFileProvider fileProvider)
    { ... }
}    

public class FtpFileProvider : IFileProvider
{
    public FtpFileProvider(IFtpSettings settings)
    { ... }
}

public class CarSalesFtpSettings : IFtpSettings { ... }
public class MotorcycleSalesFtpSettings : IFtpSettings { ... }

До сих пор я использовал привязки на основе соглашений, но этого уже недостаточно, потому что у меня есть более одной реализации для IFtpSettings. Поэтому я решил использовать некоторые контекстные привязки. На первый взгляд kernel.Bind<>().To<>().WhenInjectedInto<>() выглядел многообещающе, но это помогает только на первом уровне, а это означает, что если бы у меня были CarSalesFtpFileProvider и MotorcycleSalesFtpProvider, я мог бы сделать это:

kernel.Bind<IFtpSettings>().To<CarSalesFtpSettings>()
    .WhenInjectedInto<CarSalesFtpFileProvider>();
kernel.Bind<IFtpSettings>().To<MotorcycleSalesFtpSettings>()
    .WhenInjectedInto<MotorcycleSalesFtpFileProvider>();

Но кажется довольно глупым создание двух конкретных реализаций FtpFileProvider, которые действительно отличаются только тем, какие настройки я хочу, чтобы они использовали. Я видел, что есть метод под названием WhenAnyAnchestorNamed(string name). Но этот маршрут требует, чтобы я добавлял атрибуты и магические строки в свои пакетные задания, что меня не впечатляет.

Я также заметил, что существует простой старый метод .When(Func<IRequest, bool>) для операторов привязки, поэтому я придумал это в качестве своих операторов привязки:

//at this point I've already ran the conventions based bindings code so I need to unbind
kernel.Unbind<IFtpSettings>();
kernel.Bind<IFtpSettings>().To<CarSalesFtpSettings>()
    .When(r => HasAncestorOfType<CarSalesBatchJob>(r));
kernel.Bind<IFtpSettings>().To<MotorcycleSalesFtpSettings>()
    .When(r => HasAncestorOfType<MotorcycleSalesBatchJob>(r));

// later on in the same class
private static bool HasAncestorOfType<T>(IRequest request)
{
    if (request == null)
        return false;

    if (request.Service == typeof(T))
        return true;

    return HasAncestorOfType<T>(request.ParentRequest);
}

Таким образом, если конструктор запрашивает IFtpSettings, мы рекурсивно поднимаемся вверх по дереву запросов, чтобы увидеть, соответствуют ли какие-либо из запрошенных служб/типов в цепочке предоставленному типу (CarSalesBatchJob или MotorcycleSalesBatchJob), и если да, то возвращается true. Если мы дойдем до вершины цепочки, мы вернем false.

Извините за длинное объяснение фона.

Вот мой вопрос: есть ли причина, по которой я не должен подходить к проблеме таким образом? Считается ли это дурным тоном? Есть ли лучший способ найти типы запросов предков? Должен ли я реструктурировать свою цепочку классов/зависимостей на более «приемлемую» моду?


person viggity    schedule 06.06.2012    source источник
comment
Я считаю, что решение, которое вы указали в своем вопросе, для реализации двух конкретных классов, которые связывают FtpFileProvider с реализацией IFtpSettings, является самым простым для понимания/чтения/отладки/передачи. Другие решения требуют, чтобы разработчик знал больше о вашем конкретном инструменте IOC и о том, как вы его настроили. Эта реализация также следует Соглашению о конфигурации для Ninject.   -  person Aaron Hoffman    schedule 07.06.2012


Ответы (3)


Вы должны использовать request.Target.Member.ReflectedType вместо request.Service Это тип реализации.

Также WhenAnyAncestorNamed не требует атрибутов. Вы можете пометить привязки своих Вакансий с помощью метода Named.

person Remo Gloor    schedule 06.06.2012

На самом деле это не ответ на ваш вопрос, но вы можете решить свою проблему, написав один класс следующим образом:

private sealed class FtpFileProvider<TFileProvider>
     : FtpFileProvider
    where TFileProvider : IFileProvider
{
    public FtpFileProvider(TFileProvider settings)
        : base(settings) { }
}

В этом случае ваша конфигурация будет выглядеть так:

kernel.Bind<IFileProvider>()
    .To<FtpFileProvider<CarSalesFtpSettings>>()
    .WhenInjectedInto<CarSalesBatchJob>();

kernel.Bind<IFileProvider>()
    .To<FtpFileProvider<MotorcycleSalesFtpSettings>>()
    .WhenInjectedInto<MotorcycleSalesBatchJob>();

Обратите внимание: по своему опыту я обнаружил, что в большинстве случаев, когда вы думаете, что вам нужна инъекция на основе контекста, на самом деле у вас есть недостаток в вашем дизайне. Однако с предоставленной информацией я не могу ничего сказать об этом в вашем случае, но вы можете взглянуть на свой код. Возможно, вы сможете реорганизовать свой код таким образом, что вам на самом деле не понадобится внедрение на основе контекста.

person Steven    schedule 06.06.2012

Мой совет — нацеливаться на место назначения ситуации, нарушающей соглашение, с вашей регистрацией, а не на саму настройку IFtpSettings. Например, в похожей ситуации я бы сделал следующее:

container.Register<CarSalesBatchJob>(() => {
    ICommonSetting myCarSpecificDependency = container.Resolve<CarSpecificDependency>();
    new CarSalesBatchJob(myCarSpecificDependency);
});

container.Register<MotorcycleSalesBatchJob>(() => {
    ICommonSetting myMotorcycleSpecificDependency = container.Resolve<MotorcycleSpecificDependency>();
    new MotorcycleSalesBatchJob(myMotorcycleSpecificDependency);
});

Это настолько прямолинейно, насколько это возможно, когда дело доходит до объяснения другим программистам, как создается каждое пакетное задание. Вместо того, чтобы нацеливаться на регистрацию ICommonSetting для обработки каждого единичного экземпляра, вы обрабатываете каждый единичный экземпляр в своем собственном случае.

Другими словами, представьте, если бы эти классы имели две зависимости, которые необходимо изменить в контейнере IoC. У вас будет четыре строки регистрационного кода в разных местах, но все они предназначены для создания экземпляров MotorcycleSalesBatchJob, CarSalesBatchJob и т. д. Если кто-то захочет узнать, как ссылаются на класс, ему придется искать любая ссылка на класс (или базовый класс). Почему бы просто не написать код, который точно объясняет, как каждый из них должен быть создан, и все это в одном месте?

Недостаток этого (или, по крайней мере, то, что я слышал от других) заключается в том, что если конструктор для любого из этих конкретных классов изменится, код сломается, и вам придется изменить регистрацию. Что ж, для меня это позитив, потому что я уже сделал один шаг по этому пути с изменением контейнера IoC в зависимости от какого-то состояния, мне нужно убедиться, что я все еще сохраняя ожидаемое поведение.

Это становится еще веселее, когда вы думаете о возможностях. Вы можете сделать что-то вроде этого:

container.Register<IProductCatalog>(() => {
    currentState = container.Resolve<ICurrentState>().GetTheState();
    if (currentState.HasSpecialPricing())
       return container.Resolve<SpecialPricingProductCatalog>();
    return container.Resolve<RegularPricingProductCatalog>();
});

Всю сложность того, как что-то может работать в разных ситуациях, можно разбить на отдельные классы, предоставив контейнеру IoC возможность предоставить правильный класс в нужной ситуации.

person Darren    schedule 07.06.2012