Использование имен для различения экземпляров с использованием IoC

Я пробую Ninject и модифицирую код, который написал с помощью Structure Map, чтобы увидеть, насколько это просто. В этом базовом коде у меня есть граф объектов, которые имеют разные конфигурации через реестры карты структуры, и тот, который будет использоваться, выбирается во время выполнения через значение в базе данных (в этом случае, чтобы вернуть тело службы wcf с некоторыми введенными объектами) . Так, например (используя код карты структуры):

Реестр 1 устанавливает все значения по умолчанию для типов IBusinessContext, IRules и ILogger. Это просто добавление типов GenericContext/Logger/Rules рядом с интерфейсами без какой-либо другой специализации.

public GenericRegistry()
    {
        // Set up some generic bindings here
        For<ILogger>().Use<Loggers.GenericLogger>();
        For<IBusinessRule>().Use<Rules.StandardRule>();
        For<IBusinessContext>().Use<Contexts.GenericBusinessContext>();
        For<ILoggerContext>().Use<Loggers.GenericLoggerContext>();
    }

Реестр 2 настраивает IBusinessContext для использования класса SpecializedContext и указывает ctor использовать SpecializedLogger. Экземпляр для IBusinessContext называется SpecializedContext.

public SpecializedRegistry()
    {
        // Old style syntax as it affects the default for IBusinessContext
        // Perhaps a hint at what I'm doing?
        InstanceOf<IBusinessContext>().Is.OfConcreteType<Contexts.SpecializedBusinessContext>().Named(SpecializedInstanceName).Ctor<ILogger>().Is<Loggers.SpecialisedLogger>();
    }

Все это работает, как и ожидалось в карте структуры (в зависимости от старого или нового синтаксиса).

Однако, когда я использовал Ninject, я столкнулся с проблемой, ожидая, что неназванный экземпляр будет использоваться по умолчанию (я понимаю, как работает Ninject). Это привело к некоторым исследованиям, которые все предположили, что использование именованных экземпляров — действительно плохая идея. Я понимаю, что есть лучшие способы сделать это, используя автоматическую регистрацию или атрибуты для установки имени или запроса определенного типа, но в системе, которую я описываю, должен быть способ во время выполнения выяснить, какую конфигурацию запрашивать. в верхней части дерева (и пусть инфраструктура IoC выясняет все остальное на основе зарегистрированных типов или правил).

Итак ... я просто неправильно использую концепцию IoC, ожидая запросить мой объект верхнего уровня по имени, или вообще есть лучший способ сделать то, что я пытаюсь сделать? Должен ли я вместо этого использовать что-то вроде MEF и относиться ко всему этому как к плагинам?

Я подчеркиваю, что я не использую это как тупую фабрику и не запрашиваю на каждом уровне кода экземпляр типа x из контейнера, это только инициирующее действие.

Заранее спасибо за ваше время и помощь :)


person NoodleAwa    schedule 14.02.2012    source источник


Ответы (1)


Нет ничего плохого в настройке привязок ninject по имени, если это единственный способ добиться того, что вам нужно, IMO.

Итак, основной синтаксис:

Bind<IBusinessContext>().To<ConcreteBusinessContext>().Named("XYZ");

Или, если вам нужен конкретный вызывающий класс для получения другой привязки, вы можете попробовать:

Bind<IIBusinessContext>().To<SomeOtherConcreteBusinessContext>().WhenInjectedInto<TypeOfCallingClass>();

Однако, если вызывающий класс (я говорю о классе, который имеет IBusinessContext в своем ctor) предоставляет значение конфигурации, которое определяет, какой конкретный тип загружать, тогда вам нужно будет использовать делегат:

Bind<Func<string, IBusinessContext>>().ToMethod(ctx => str => DetermineWhichConcreteTypeToLoad(ctx, str));

//messy sudo code
static DetermineWhichConcreteTypeToLoad(IContext ctx, string str)
{
    if(str == "somevalue"){
        return ctx.Kernel.Get<ConcreteType1>();
    else
        return ctx.Kernel.Get<ConcreteType2>();
}

и ваш вызывающий класс будет выглядеть примерно так:

class DoStuff
{
    Func<string, IBusinessContext>> contextFunc;

    DoStuff(Func<string, IBusinessContext>> contextFunc)
    {
        this.contextFunc = contextFunc;
    }

    void SomeMethod()
    {
        var configuredValue = GetConfiguredValueSomehow();
        var context = contextFunc(configuredValue); //<-- this passes your config value back to ninject in the ToMethod() lambda
        //do something with context
    }
}

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

Bind<IBusinessContext>().To<ConcreteBusinessContext>().Named("config1");
Bind<IBusinessContext>().To<SomeOtherBusinessContext>().Named("config2");

Bind<Func<string, IBusinessContext>>().ToMethod(ctx => str => ctx.Kernel.Get<IBusinessContext>().Named(str));

class DoStuff
{
    Func<string, IBusinessContext>> contextFunc;

    DoStuff(Func<string, IBusinessContext>> contextFunc)
    {
        this.contextFunc = contextFunc;
    }

    void SomeMethod()
    {
        var configuredValue = "config1";
        var context = contextFunc(configuredValue); //<-- this will passthrough "config1" to the above ToMethod() method and ask for a IBusinessContext named "config1"

    }
}

РЕДАКТИРОВАТЬ: я забыл упомянуть, что если значение вашей конфигурации не должно исходить из кода вызова, то это значительно упрощает задачу. Вместо этого ваш код может выглядеть примерно так:

// this method can just be a global method in you app somewhere
static string GetConfigValue()
{
    //something like
    return AppSetting.Get("config");
}

Bind<IBusinessContext>().To<ConcreteBusinessContext>().When(r => GetConfigValue() == "config1");
Bind<IBusinessContext>().To<SomeOtherBusinessContext>().When(r => GetConfigValue() == "config2");

class DoStuff
{
    IBusinessContext context;

    DoStuff(BusinessContext context)
    {
        this.context = context;
    }

    void SomeMethod()
    {
        //use the context value as you normally would
    }
}

Вы можете проявить творческий подход и вместо того, чтобы использовать магические строки, ваш метод конфигурации может загрузить перечисление, а ваш метод When() может проверять равенство с перечислением вместо строки, но вы поняли идею. Это известно как контекстная привязка в ninject, и я могу сказать вам, как когда-то заядлый пользователь SM, это намного мощнее, чем все, что было в SM. Ознакомьтесь с остальными методами When() и посмотрите, что вы можете сделать.

person AaronHS    schedule 15.02.2012
comment
Спасибо Аарон! Это прояснило для меня некоторые вещи :) Это очень ценно. - person NoodleAwa; 15.02.2012