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

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


Получение сервиса по идентификатору (Resolving service by ID)

Многие популярные IoC-фреймворки предоставляют примерно следующий функционал, позволяющий присваивать имена конкретным типам, реализующим интерфейсы
var container = new UnityContainer(); // да простят меня ненавистники Unity...

container.RegisterType<IService, LocalService>("local");
container.RegisterType<IService, CloudService>("cloud");

IService service;

if (context.IsLocal)
{
    service = container.Resolve<IService>("local");
}
else
{
    service = container.Resolve<IService>("cloud");
}

или так
public class LocalController
{
    public LocalController([Dependency("local")] IService service) 
    {
        this.service = service;
    }
}

public class CloudController
{
    public CloudController([Dependency("cloud")] IService service) 
    {
        this.service = service;
    }
}

Это позволяет выбирать нужную нам реализацию в зависимости от контекста.

Встроенный в ASP.NET Core инъектор зависимостей поддерживает множественную реализацию, но к сожалению, не имеет возможности присваивать идентификаторы для отдельной реализации. К счастью :) можно самим реализовать разрешение сервиса по идентификатору, написав немного кода.

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

Это будет словарь такого вида
Dictionary<Type, Dictionary<string, Type>>


Здесь ключом словаря будет тип интерфейса, а значением — словарь, в котором (прошу прощения за тавтологию) ключом будет идентификатор, а значением тип реализации интерфейса.

Добавлять сервисы в эту структуру будем следующим образом

private static readonly Dictionary<Type, Dictionary<string, Type>> ServiceNameMap =
    new Dictionary<Type, Dictionary<string, Type>>();

public static void RegisterType(Type service, Type implementation, string name)
{
    if (ServiceNameMap.ContainsKey(service))
    {
        var serviceNames = ServiceNameMap[service];

        if (serviceNames.ContainsKey(name))
        {
            /* overwrite existing name implementation */
            serviceNames[name] = implementation;
        }
        else
        {
            serviceNames.Add(name, implementation);
        }
    }
    else
    {
        ServiceNameMap.Add(service, new Dictionary
        {
            [name] = implementation
        });
    }
}


И вот так получать сервис из контейнера (как вы помните из предыдущей статьи, IoC-контейнер в ASP.NET Core представлен интерфейсом IServiceProvider)

public static TService Resolve<TService>(IServiceProvider serviceProvider, string name)
    where TService : class
    {
        var service = typeof(TService);

        if (service.IsGenericType)
        {
            return ResolveGeneric<TService>(serviceProvider, name);
        }

        var serviceExists = ServiceNameMap.ContainsKey(service);
        var nameExists = serviceExists && ServiceNameMap[service].ContainsKey(name);

        /* Return `null` if there is no mapping for either service type or requested name */
        if (!(serviceExists && nameExists))
        {
            return null;
        }
        return serviceProvider.GetService(ServiceNameMap[service][name]) as TService;
}


Теперь все, что остается сделать, это написать набор методов расширения для удобной настройки контейнера, например

public static IServiceCollection AddScoped<TService, TImplementation>(this IServiceCollection services, string name)
    where TService : class
    where TImplementation : class, TService
{
    ServiceNamesMap.RegisterType<TService, TImplementation>(name);
    return services.AddScoped<TImplementation>();
}


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

public static TService GetService(this IServiceProvider serviceProvider, string name)
    where TService : class
{
    return ServiceNamesMap.Resolve(serviceProvider, name);
}


Все готово для исползьования

services.AddScoped<IService, LocalhostService>("local");
services.AddScoped<IService, CloudService>("cloud");

var service1 = this.serviceProvider.GetService<IService>("local"); // will resolve LocalhostService
var service2 = this.serviceProvider.GetService<IService>("cloud"); // will resolve CloudService


Можно пойти еще немного дальше и создать аттрибут, позволяющий производить инъекцию в параметр экшена, наподобие аттрибута MVC Core [FromServices] вот с таким синтаксисом

public IActionResult Local([FromServices("local")] IService service) { ... }


Для того, чтобы реализовать такой подход, нужно немного глубже разобраться в процессе Привязки модели (Model binding) в ASP.NET Core

Коротко говоря, аттрибут параметра определяет, какой ModelBinder (класс, реализующий интерфейс IModelBinder) будет создавать объект параметра. Например, аттрибут [FromServices], входящий в состав ASP.NET Core MVC, указывает на то, что для привязки модели будет использован IoC-контейнер, и следовательно для этого параметра будет использован класс ServicesModelBinder, который попытается получить тип параметра из IoC-контейнера.

В нашем случае, мы создадим два дополнительный класса. Первый — это ModelBinder, который будет получать сервис из IoC-контейнера по идентификатору, а второй — свой собственный аттрибут FromServices, который будет принимать в конструкторе идентификатор сервиса, и который будет указывать на то, что для привязки следует использовать определенный ModelBinder, который мы создали.

[AttributeUsage(AttributeTargets.Parameter)]
public class FromServicesAttribute : ModelBinderAttribute
{
    public FromServicesAttribute(string serviceName)
    {
        this.ServiceName = serviceName;
        this.BinderType = typeof(NamedServicesModelBinder);
    }
    public string ServiceName { get; set; }
    public override BindingSource BindingSource => BindingSource.Services;
}

public class NamedServicesModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var serviceName = GetServiceName(bindingContext);

        if (serviceName == null) return Task.FromResult(ModelBindingResult.Failed());

        var serviceProvider = bindingContext.HttpContext.RequestServices;
        var model = serviceProvider.GetService(bindingContext.ModelType, serviceName);

        bindingContext.Model = model;
        bindingContext.ValidationState[model] = new ValidationStateEntry { SuppressValidation = true };
        bindingContext.Result = ModelBindingResult.Success(model);

        return Task.CompletedTask;
    }

    private static string GetServiceName(ModelBindingContext bindingContext)
    {
        var parameter = (ControllerParameterDescriptor)bindingContext
            .ActionContext
            .ActionDescriptor
            .Parameters
            .FirstOrDefault(p => p.Name == bindingContext.FieldName);

        var fromServicesAttribute = parameter
            ?.ParameterInfo
            .GetCustomAttributes(typeof(FromServicesAttribute), false)
            .FirstOrDefault() as FromServicesAttribute;

        return fromServicesAttribute?.ServiceName;
    }
}


На этом все :) Исходный код примеров можно скачать по ссылке:

github.com/izaruba/AspNetCoreDI
Поделиться с друзьями
-->

Комментарии (23)


  1. boblenin
    03.08.2016 19:54
    +1

    А в чем value-add если сравнивать с обычным new? У вас так же присутствует условное ветвление. Для изменения реализации вам все еще нужно пересобирать и переставлять приложение. Сомневаюсь, что в обозримом будующем у вас изменится поведение для "local" и тем более для "cloud".


    Может быть я чего-то не понимаю. Подскажите зачем?


    1. ivanzaruba
      03.08.2016 22:07

      С удовольствием подскажу :)

      Смотрите:

      А в чем value-add если сравнивать с обычным new?

      этот пример показывает только механизм внедрения по идентификатору, поэтому используются очень простые классы, только для примера. Но допустим, в сервисах у нас в конструктор будут внедряется другие зависимости (сериализатор, низкоуровневый сервис для работы с файловой системой — дисковой или облачной, доступ к базе данных и т.д.). Если я буду использовать new, чтобы создать класс сервиса, мне придётся создать вручную и все зависимости, а так все сделает инъектор зависимостей, исходя из настроек IoC-контейнера. И нельзя не упомянуть об удобстве тестирования таких классов.

      Для изменения реализации вам все еще нужно пересобирать и переставлять приложение.

      Реализация может находиться в отдельной сборке, поэтому для ее изменения мне достаточно будет поменять сборку в папке с билдом, не пересобирая все приложение. Кроме того, можно создать ещё один уровень абстракции, если вынести настройку контейнера в отдельную сборку. Т.о. веб приложение вообще не будет завязано на конкретные реализации, а типы сервисов станут доступны только во время выполнения.

      Сомневаюсь, что в обозримом будующем у вас изменится поведение для «local» и тем более для «cloud».

      Поведение может не измениться, но может измениться технология. Например работали с Amazon и решили перейти на Azure. Веб приложение останется без изменений, т.к. реализации сервисов (и настройка контейнера) находятся в отдельной сборке.

      Надеюсь, я правильно понял Ваши вопросы. Если что-нибудь остается непонятным, спрашивайте — с удовольствием отвечу :)


      1. jack128
        04.08.2016 10:09

        вот чем лучше: public LocalController([Dependency(«local»)] IService service)
        по сравнению с public LocalController(LocalService service) ??

        В первом случае мы закладываемся на какую то магическую строку, по которой обязательно должен быть создал локальный сервис(не просто так же в атрибуте строка «local» сидит). Во втором случае — такой зависимости нет. В чём прикол?


        1. ivanzaruba
          04.08.2016 12:21

          LocalService это конкретный тип, а IService со строкой это абстракция

          В чём прикол?


          Не стоит рассматривать эти классы, как решение, которое выполняет что-то конкретное. Это демонстрация архитектуры приложения, а не определенного функционала.

          public LocalController([Dependency(«local»)] IService service) 
          

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

          Например, у нас приложение рисует графики. Есть такой контроллер
          ChartsController
          public class ChartsController : Controller
          {
              public IActionResult PieChart([FromServices("pie")] IChartService chartService)
              {
                  return this.View(chartService.GetData());
              }
          
              public IActionResult BarChart([FromServices("bar")] IChartService chartService)
              {
                  return this.View(chartService.GetData());
              }
          
              public IActionResult LineChart([FromServices("line")] IChartService chartService)
              {
                  return this.View(chartService.GetData());
              }
          }
          


          1. jack128
            04.08.2016 12:29

            Хм, я бы просто сделал PieChartService/BarChartService/LineChartService. Реализовал бы из используя Google Charts. Решил бы поменять фреймвок — ну поменял бы реализацию этих классов.


            1. ivanzaruba
              04.08.2016 14:08

              В этом случае вы просто задаете асбстракцию типом класса вместо интерфейса.

              Конечно, абстракцию можно задать и классом, иногда даже не абстрактным (тот же DbContext).
              Вопрос выбора интерфейса или абстрактного класса, это уже другая история.

              я бы просто сделал PieChartService/BarChartService/LineChartService

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

              Например, мы можем изменить контроллер
              ChartsController
              public class ChartsController : Controller
              {
                  public IActionResult GetChart([FromServices] IServiceProvider services, string chartType)
                  {
                      var chartService = services.Resolve<IService>(chartType);
                      return this.View(chartService?.GetData());
                  }
              }
              


      1. boblenin
        04.08.2016 13:30

        Если я буду использовать new, чтобы создать класс сервиса, мне придётся создать вручную и все зависимости, а так все
        сделает инъектор зависимостей, исходя из настроек IoC-контейнера.

        Ну я знаком с IoC и DI. И насмотрелся на множество ситуаций, когда, как мне кажется, использование IoC конейнеров нисколько не помогает, а вот мешает существенно. Ваш код, как я понял — это демонстрация возможностей, но я собственно о том и пишу. Использование продемонстрированых техник для описаной задачи выглядит как стрельба из пушки по воробьям.


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

        Собственно в чем плюс возможности пересборки одной библиотеки? Это DI конфигурация. Как минимум после ее изменения нужно прогнать полный кейс автоматизированых тестов, а как максимум произвести регрессионное тестирования. Изменяя эту конфигурацию — вы меняете поведение всей системы. А учитывая сценарии вроде описанного вами — вы еще и можете изменить поведение в случайных местах (вы решаете какую реализацию использовать не из DI контейнера, а ветвлением).


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


        Кроме того, можно создать ещё один уровень абстракции, если вынести настройку контейнера в отдельную сборку. Т.о. веб
        приложение вообще не будет завязано на конкретные реализации, а типы сервисов станут доступны только во время
        выполнения.

        Вы себе представляете как потом это тестировать? А воспроизводить баги?


        Поведение может не измениться, но может измениться технология. Например работали с Amazon и решили перейти на
        Azure.

        Ага. И после перехода вам не надо будет все тестировать, переписывать запросы для оптимизации?! Помните принцип YAGNI? Вы внедряете весьма тяжелую зависимость ради гипотетической возможности. В случае если вы пишете enterprise — такие переходы маловероятны. В случае если вы пишете high-load — использование DI/IoC контейнера — это серьезная растрата производительности, а значит денег клиента.


        Мало того адекватное использование DI/IoC контейнера в приложении — это когда DI/IoC конейнер можно поменять или вообще убрать не переписывая практически все приложение. Что я вижу в вашем случае — вы используете ServiceLocator паттерн — а это вообще жесть. Он в общем для другого.


        1. ivanzaruba
          04.08.2016 14:46

          использование IoC конейнеров нисколько не помогает, а вот мешает существенно

          не могу согласиться :)

          Использование продемонстрированых техник для описаной задачи выглядит как стрельба из пушки по воробьям.
          А учитывая сценарии вроде описанного вами
          Что я вижу в вашем случае — вы используете ServiceLocator паттерн — а это вообще жесть. Он в общем для другого.

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

          Вы себе представляете как потом это тестировать? А воспроизводить баги?

          Конечно представляю. А в чем по-вашему проблема?

          Ага. И после перехода вам не надо будет все тестировать, переписывать запросы для оптимизации?!
          В случае если вы пишете enterprise — такие переходы маловероятны. В случае если вы пишете high-load — использование DI/IoC контейнера — это серьезная растрата производительности, а значит денег клиента.

          Это вопрос требований. И принятие решений об архитектуре приложения строится на основе этих требований. Если использование DI/IoC не оправдано или не удовлетворяет требуемой производительности, его не используют. Суть статьи не в том, чтобы сказать «Все используйте IoC!», а в том, чтобы показать, как реализовать знакомый по другим IoC-фреймовркам функционал внедрения по идентификатору. Я же не заставляю всех это использовать везде, где бы то ни было :)


          1. boblenin
            05.08.2016 00:15

            не могу согласиться :)

            Не можете согласиться с тем, что во многих ситуациях IoC и DI неуместны? Перефразируя можно ли вас понять так что: "IoC и DI — всегда уместны" или "IoC и DI чаще всего уместны"?


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

            Может быть реализация динамических плагинов?


            Конечно представляю. А в чем по-вашему проблема?

            В количестве тест кейсов. Любые динамические конфигурации требуют тестирования всех комбинаций. Ручное тестирование в таком случае сразу отсекаем, но даже написание автоматических тестов все против всех — это суровый такой труд.


            Может быть вы представляете себе это по другому? Как по-вашему надо подходить к вопросу?


            Суть статьи не в том, чтобы сказать «Все используйте IoC!», а в том, чтобы показать, как реализовать знакомый по другим IoC-
            фреймовркам функционал внедрения по идентификатору.

            Ну т.е. вы реализовали на IoC конейнере паттерн Factory. Очень хорошо.


            Я же не заставляю всех это использовать везде, где бы то ни было :)

            И это замечательно.


            1. ivanzaruba
              05.08.2016 11:55

              Не можете согласиться с тем, что во многих ситуациях IoC и DI неуместны? Перефразируя можно ли вас понять так что: «IoC и DI — всегда уместны» или «IoC и DI чаще всего уместны»?

              Я согласен с тем, что бывают ситуации, когда использование DI неуместно. Это индивидуально для каждого проекта.

              Может быть реализация динамических плагинов?

              Хорошая идея, спасибо большое! Попробую :)

              Любые динамические конфигурации требуют тестирования всех комбинаций.
              Может быть вы представляете себе это по другому? Как по-вашему надо подходить к вопросу?

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

              Ну т.е. вы реализовали на IoC конейнере паттерн Factory. Очень хорошо.

              А плохого что?)) И это не Factory, скорее Service Locator. Во-первых, Factory подразумевает создание однотипных объектов. Во-вторых, Factory создает объекты, в то время как Service Locator не обязательно будет их создавать сам. IoC-контейнер может реализовывать паттерн Service Locator, при необходимости. Поэтому его можно спутать с паттерном Factory.


              1. boblenin
                05.08.2016 21:09

                Я говорю не о динамической замене одной сборки на другую в ходе выполнения.

                И я о них же. Зависит, конечно, от того, как вы поставляете софт. Если кастомные установки, с доводкой под клиента, то тестировать нужно все возможные конфигурации. Если же вы продаете сервис и набор конфигураций заранее известен, то смысла делать их динамическими нет. Принцип KISS.


                IoC-контейнер может реализовывать паттерн Service Locator, при необходимости.

                Вообще существует мнение, что ServiceLocator — это антипаттерн.


                1. ivanzaruba
                  07.08.2016 12:57

                  Вообще существует мнение, что ServiceLocator — это антипаттерн.

                  Я с этим мнением согласен. Имхо, антипаттерном он является тогда, когда метод Resolve вызывается многократно при каждом удобном случае. Но если он разумно используется для реализации DI и при этом выполняет свою функцию, то что в этом плохого? Какие есть варианты получше?


                  1. Lailore
                    07.08.2016 20:57
                    +1

                    В том и дело, что если использовать сервис локатор правильно, то он будет неотличим от контейнера(Не будет зависимостей от локатора). В свою очередь практически все контейнеры позволяют использовать себя как сервис локаторы.

                    Кстати, я так и не нашел решения как обходится без сервис локатора(Резолва вне композиции объектов), когда нужны фабрики и отсутствует возможность автоматической генерации.

                    Регистрировать Func и т.п. мне кажется крайне уродливым.


                    1. ivanzaruba
                      07.08.2016 21:27

                      Абсолютно согласен, плюсую :)


                  1. boblenin
                    08.08.2016 15:39

                    Для реализации DI контейнера пожалуй других вариантов и нет (нет ну можно конечно конструировать объекты через рефлексию, или там извратиться с Func или пачку фабрик понасоздавать; но будет еще кривее).


                    А вот для реализации DI самого по себе — ничего не надо. Либо DI контейнер с отдельной конфигурацией, когда надо динамически менять поведение — либо new, когда не надо.


                    1. Lailore
                      08.08.2016 15:57

                      Ага, а потом попробуйте добавить например кеш на какой-нибудь сервис. И в итоге ваш сервис будет заботится о своей непосредственной работе, а так же о кеше. Это явно дурно пахнущий код.

                      С Ди это решается на раз с помощью декоратора или даже перехвата. А если же вы захотите использовать декоратор, то вам придется проштудировать весь исходный код в поисках new. Так же вам нужно будет шерстить весь код, если классу понадобилась новая зависимость. Кстати, эту зависимость еще надо пробросить до сервиса. А как обстоят дела с временем жизни зависимостей? При new вам придется кропотливо за всем следить и управлять этим руками.


                      1. boblenin
                        08.08.2016 18:52

                        Ага, а потом попробуйте добавить например кеш на какой-нибудь сервис. И в итоге ваш сервис будет заботится о своей
                        непосредственной работе, а так же о кеше. Это явно дурно пахнущий код.

                        Вы про то, как например в Java сделана BufferedStream? Что же в нем такого уж дурно пахнущего? Опять же если вы кэш делаете, то видимо и инвалидацию — она у вас ведь не по таймауту?


                        С Ди это решается на раз с помощью декоратора или даже перехвата

                        Видел такие реализации. Кошмар дикий. Либо начинается протечка абстракции (abstraction leak) либо правила инвалидации настолько дубовые, что польза от кэша становится сомнительной.


                        Так же вам нужно будет шерстить весь код, если классу понадобилась новая зависимость.

                        А можете поподробнее? Как это вы так структурируете код, что добавление зависимости требует что-то шерстить?


                        А как обстоят дела с временем жизни зависимостей? При new вам придется кропотливо за всем следить и управлять
                        этим руками.

                        А как обстоят дела с наследованием и несколькими реализациями интерфейса? Вы же эти правила прописываете в кофигурации контейнера? А значит вам придется кропотливо следить за тем в каком случае кто и как решил отрезолвить интерфейс (в одном случае нам тот же кэш нужен, а в другом нет или другой кэш). Конфигурация видимо инициализируется на запуске приложения (привет многопоточность), либо на запуске каждого инстанса (привет производительность).


                        Ну а если у вас строго один класс — один интерфейс, то это как раз тот самый code smell.


                        1. Lailore
                          09.08.2016 05:24
                          +1

                          Вы про то, как например в Java сделана BufferedStream? Что же в нем такого уж дурно пахнущего? Опять же если вы кэш делаете, то видимо и инвалидацию — она у вас ведь не по таймауту?

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

                          Например сервису «карты» понадобилось отправлять сообщения пользователям при каких-то событиях. Если мы используем ДИ, то мы просто добавляем в конструктор параметр IMessageCenter. А если мы используем new, то надо искать везде этот new и добавлять туда новый параметр, к тому же надо что бы вызывающий метод имел доступ к IMessageCenter(И он например создается один на webrequest).

                          А как обстоят дела с наследованием и несколькими реализациями интерфейса? Вы же эти правила прописываете в кофигурации контейнера? А значит вам придется кропотливо следить за тем в каком случае кто и как решил отрезолвить интерфейс (в одном случае нам тот же кэш нужен, а в другом нет или другой кэш). Конфигурация видимо инициализируется на запуске приложения (привет многопоточность), либо на запуске каждого инстанса (привет производительность).

                          Да, все это настраивается в композиции. В ОДНОМ месте. А не разбросаны по всему коду. Что насчет производительности, то это почти никогда не вызывает проблем, а если вам выпал шанс один на миллиард, то с этим можно справится(Я не могу придумать ситуации где это будет сделать трудно)


                          1. boblenin
                            09.08.2016 19:59

                            Инвалидацией занимается тотже декоратор.

                            Т.е. ваш декоратор знаком со всеми сценариями использования данных для каждого сервиса, для которого он сконфигурирован? Вот это уже анти-паттерн God-object.


                            Дурной запах в том, что при добавлении кеша сервис начинает выполнять две задачи: свою работу и заботу о
                            кеше.

                            С чего бы вдруг? BufferedStream выполняет одну задачу — занимается управлением кэшем. Сервис видит все как Stream и вообще не парится. По-моему очень хороший баланс между контролем и расширяемостью.


                            Если мы используем ДИ, то мы просто добавляем в конструктор параметр IMessageCenter.

                            Если мы используем DI или DI-контейнер? Одно-дизайн, а другое реализация. Встречный вопрос вам: "А если IMessageCenter имеет несколько реализаций для разных сценариев — будете делать как в статье?"


                            А если мы используем new, то надо искать везде этот new и добавлять туда новый параметр

                            Не надо. У вас есть перегрузка конструкторов, фабрики итд.


                            Если все, что вам нужно — это добавить одну дефолтную реализацию IMessageCenter, то перегрузите конструктор с доп методом (для unit testing), а в старый добавте дефолтный параметр. DI у вас остается, а контейнер уже не так уж и нужен, впрочем тоже возможен.


                            Вообще ваш пример — один из самых неудачных. Проблема в том, что Message, в обычном состоянии, — это короткоживущий объект. Тот, который должен всегда находиться в поколении молодежи для Garbage Collector. С ним в нормальной ситуации GC легко справится — потратит время только на обход, но то, что вы сделаете с DI контейнером или ServiceLocator — вы его сделаете долгоживущим — а значит вы не только заплатите доп. цену на создании объекта, но еще и на сборке и на поддержании в живом состоянии (дефрагментацию).


                            Опять же. Если вы колбасите какой-нибудь MVС, чтоб на 10 пользователей и чтобы крутилось на 32 ядрах и 512Gb оперативки — вам может быть и без разницы, но опять же сложность вашего кода тогда не на столько высока чтобы оправдать отдельную конфигурацию для сборки объектов и активно внедряя DI вы скорее всего усложняете проект, а значит зря тратите деньги заказчика, а еще важнее его время (time to market) — клепайте, чтоб работало.


                            Да, все это настраивается в композиции. В ОДНОМ месте.

                            Вам нужно все в одном месте… в смысле God-object — это ваша цель? Если так — то это ваш и вашего тех. лида или архитектора выбор. Я бы в наказание за такое дело ставил разработчиков сопровождать этот код в течении нескольких лет без права перехода на другую работу.


                            Может быть вам нужен один файл и вы почему-то не можете объявить свои классы partial и вытащить конструкторы в этот отдельный файл? Думаете внедрять глобальный статический словарь, ломать стандартное поведение GC, огрести проблемы с отладкой и многопоточностью того стоит? Ну опять же ваш выбор.


                            Что насчет производительности, то это почти никогда не вызывает проблем

                            Если у вас нет проблем с производительностью — зачем вы занялись кэшированием? Это было решением продиктованым статистикой и сценариями доступа к данным или "закэширую чтоб два раза не ходить"?


                            1. Lailore
                              09.08.2016 22:50

                              Мы явно не понимаем друг друга ;(. Ни о каких God-object'ах не идет речи. Я не готов писать развернутый ответ, так как для этого потребуется написать книгу. Например https://habrahabr.ru/company/piter/blog/192348/


                              1. boblenin
                                10.08.2016 01:09

                                Ни о каких God-object'ах не идет речи.

                                Либо вам не доводилось видеть проектов после пары комманд, где кто-то активно любил DI контейнеры, либо вы игнорировали очевидное.


                                Спасибо за ссылку. Предпочитаю читать в оригинале и от отцов-основателей. Кого-нибудь вроде Фаулера.


                                Надеюсь мы с вами побеседуем как-нибудь в следующий раз.


  1. RouR
    04.08.2016 11:26

    Аналог [Dependency(«local»)] для Autofac может кто подсказать?


    1. ivanzaruba
      04.08.2016 11:51
      +1

      Посмотрите здесь
      http://docs.autofac.org/en/latest/advanced/keyed-services.html

      Аттрибут [WithKey(«local»)]