Использование имен для различения экземпляров с использованием IoC - PullRequest
6 голосов
/ 14 февраля 2012

Я пробую 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 для использования класса SpecialisedContext и указывает 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 из контейнера, это только инициирующее действие.

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

1 Ответ

4 голосов
/ 15 февраля 2012

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

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

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

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

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

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

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"

    }
}

EDIT:Я забыл упомянуть, что если ваше значение конфигурации не должно исходить из вызывающего кода, то это делает вещи намного проще.Вместо этого ваш код может выглядеть примерно так:

// 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
    }
}

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

...