DI и IoC для начинающих, часть 2

    В продолжение темы по DI/IoC, мы посмотрим на сложные примеры использования Unity в нетривиальных сценариях конфигурации объектов.

    Давайте в очередной раз напишем мега-сложный сервис:

    public class MyService<br/>
    {<br/>
      [Dependency]<br/>
      public Random MyRandom { get; set; }<br/>
    }<br/>
     <br/>
    ⋮<br/>
     <br/>
    var uc = new UnityContainer();<br/>
    var svc = uc.Resolve<MyService>();<br/>
    Console.WriteLine(svc.MyRandom.Next());<br/>
    Попытка создать объект типа Random через DI потерпит неудачу ибо у Random есть несколько конструкторов и Unity, несмотря на очевидность того что надо бы вызывать пустой (т.е. new Random()) этого не делает, а пытается вызвать самый «навороченный» и терпит фиаско. Как это пофиксить? Примерно вот так:

    // заставляем собирать Random с дефолтным конструктором
    uc.RegisterType<Random>(new InjectionConstructor());<br/>
    А если бы Random был нашим классом, можно было бы еще и вот так написать:

    class Random<br/>
    {<br/>
      [InjectionConstructor]<br/>
      Random() {  }<br/>
      ⋮<br/>
    }<br/>
    Мы только что намекнули Unity что нужно использовать «пустой» конструктор. Что ж, попробуем воспльзоваться новым функционалом:

    var svc  = uc.Resolve<MyService>();<br/>
    var svc2 = uc.Resolve<MyService>();<br/>
    Console.WriteLine(<br/>
      ReferenceEquals(svc.MyRandom, svc2.MyRandom));<br/>
    В консоль будет написано значение False т.к. дефолтное поведение контейнера – каждый раз создавать новый объект. Если вы считаете, что одного Парк-энд-Миллеровского Random в принципе должно хватить на весь проект, то как получить singleton? Да очень просто:

    uc.RegisterType<Random>(<br/>
      new ContainerControlledLifetimeManager(),<br/>
      new InjectionConstructor());<br/>
    var svc  = uc.Resolve<MyService>();<br/>
    var svc2 = uc.Resolve<MyService>();<br/>
    Console.WriteLine(<br/>
      ReferenceEquals(svc.MyRandom, svc2.MyRandom));<br/>
    Этот кусочек кода уже напишет в консоль True. Параметр, наследующий от LifetimeManager определяет сколько будет жить объект. От него полезно наследовать если хочется, например, чтобы для каждого потока/сессии/обращения выдывался новый объект.

    Как вы уже наверное догадались, DI будет автоматически работать только для reference types. Код приведенный ниже работать не будет:

    public interface IService<br/>
    {<br/>
      int GetN();<br/>
    }<br/>
     <br/>
    public class MyService : IService<br/>
    {<br/>
      public int n;<br/>
      public MyService(int n)<br/>
      {<br/>
        this.n = n;<br/>
      }<br/>
      public int GetN() { return n; }<br/>
    }<br/>
    ⋮<br/>
    var uc = new UnityContainer();<br/>
    uc.RegisterType<IService, MyService>();<br/>
    uc.RegisterType<int>(new InjectionConstructor(10));<br/>
    var svc = uc.Resolve<IService>();<br/>
    Console.Write(svc.GetN());<br/>
    К сожалению, у System.Int32 не нашлось конструктора и поэтому этот код, который кстате компилируется без проблем, работать не будет. На самом деле, мы просто выбрали неверный аттрибут – вместо того чтобы манипулировать созданием Int32, в данном случае нужно управлять созданием IService:

    uc.RegisterType<IService, MyService>(<br/>
      new InjectionConstructor(<br/>
        new InjectionParameter(10)));<br/>
    Все это были достаточно очевидные манипуляции конструкторов, посмотрим на пример посложнее. Допустим у вас есть два сервиса, и оба реализуют IService:

    public interface IService<br/>
    {<br/>
      void DoSomething();<br/>
    }<br/>
    public class MyService : IService<br/>
    {<br/>
      public void DoSomething()<br/>
      {<br/>
        Console.WriteLine("My service");<br/>
      }<br/>
    }<br/>
    public class OtherService : IService<br/>
    {<br/>
      public void DoSomething()<br/>
      {<br/>
        Console.WriteLine("Other service");<br/>
      }<br/>
    }<br/>
    Теперь создадим класс который потребляет эти сервисы:

    public class Consumer<br/>
    {<br/>
      IService[] services;<br/>
      public Consumer(IService[] services)<br/>
      {<br/>
        this.services = services;<br/>
      }<br/>
      public void DoEverything() <br/>
      {<br/>
        foreach (var s in services)<br/>
         s.DoSomething();<br/>
      }<br/>
    }<br/>
    Попытка зарезолвить Consumer и вызвать DoEverything не приведет ни к чему – Unity понятия не имеет, что неплохо было бы зарезолвить IService как вытяжку всех зарегистрированных типов IService, и поэтому в конструктор передаст new IService[0]. Контейнеру опять приходится помогать:

    uc.RegisterType<Consumer>(new InjectionConstructor(<br/>
      new ResolvedParameter<IService[]>()));<br/>
    Вот пока и все. Продолжение следует!
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 18
    • +2
      как-то все слишком сложно по сравнению с MEF
      • 0
        Спасибо. Очень полезная статья. По больше бы таких.
        • 0
          Вы знаете, при всей моей любви к принципу IoC, простые примеры с контейнерами, к сожалению, явно не идут на пользу популяризации такой модели сборки ПО из компонент :(
          • 0
            Почему?
            • +1
              Потому что для таких простых примеров

              var svc = new MyService(10);

              выглядит куда лучше, чем

              var uc = new UnityContainer();
              uc.RegisterType<IService, MyService>();
              uc.RegisterType(new InjectionConstructor(10));
              var svc = uc.Resolve();

              Пользователь скажет, что он «в гробу видел ваши контейнеры», и будет прав =)

              P.S. Кстати, как в свете описанного примера будет выглядеть такой код, если его переписать на использование Unity?

              var svc10 = new MyService(10);
              var svc15 = new MyService(15);
              • +2
                На мой взгляд, человек, заинтересовавшийся IoC, через призму небольших примеров, видит то, как он будет применять IoC в своих реальных проектах, и его вряд ли отпугнут такие примеры.
                • –1
                  Вот и вы употребляете «отпугнуть», что как бэ намекаэ… :)

                  >> P.S. Кстати, как в свете описанного примера будет выглядеть такой код, если его переписать на использование Unity?

                  А на этот вопрос вы не могли бы ответить?
              • –1
                :))) этапять
          • +1
            не могли бы вы к этой статье и к последующим, добавить ссылки на предыдущие части?!
          • 0
            А подскажите, пожалуйста, как сконфигурировать только через xml синглтон, реализующий два интерфейса?

            class Impl : IA, IB {}

            Я пробовал так:
            <register type="Impl">
            <lifetime type="singleton" />
            </register>

            <register type="IA" mapTo="Impl"/>
            <register type="IB" mapTo="Impl"/>

            Интстансы разные получились отчего-то :(
            Кодом это просто сделать… а хочется декларативненько, через xml
            • 0
              Декларативность в XML на практике бесполезна и аукается потом кучей проблем при сопровождении. Поэтому если хочется DI/IoC то конфигурировать нужно через код. А если все таки нужна динамическая подмена компонентов — тогда смотреть MEF. А все эти страшные xml конфиги — ошибка природы.
              • 0
                Сделал в итоге дефолтную конфигурацию в коде, с возможностью переопределить в xml в экстренном случае.
            • 0
              пытался реализовать последний пример и не получается: myService.Randoms показывает Random[0]
              где ошибка?

              IUnityContainer container = new UnityContainer();
              container.RegisterType(
              new InjectionConstructor()); // use default constructor for new Random()
              container.RegisterType(
              new InjectionConstructor()); // use default constructor for new Random()
              container.RegisterType
              (new InjectionConstructor(
              new ResolvedParameter()));

              MyServiceWithArray myService = container.Resolve();
              foreach (Random random in myService.Randoms)
              {
              Console.WriteLine(«Next: » + random.Next());
              }
              }

              public class MyServiceWithArray
              {
              public Random[] Randoms { get; private set; }
              public MyServiceWithArray(Random[] randoms)
              {
              Randoms = randoms;
              }
              }
              public class MyRandom: Random
              {
              }

              • 0
                пытался реализовать последний пример и не получается: myService.Randoms показывает Random[0]
                где ошибка?

                IUnityContainer container = new UnityContainer();
                container.RegisterType(
                new InjectionConstructor()); // use default constructor for new Random()
                container.RegisterType(
                new InjectionConstructor()); // use default constructor for new Random()
                container.RegisterType
                (new InjectionConstructor(
                new ResolvedParameter()));

                MyServiceWithArray myService = container.Resolve();
                foreach (Random random in myService.Randoms)
                {
                Console.WriteLine(«Next: » + random.Next());
                }

                public class MyServiceWithArray
                {
                public Random[] Randoms { get; private set; }
                public MyServiceWithArray(Random[] randoms)
                {
                Randoms = randoms;
                }
                }
                public class MyRandom: Random
                {
                }

                • 0
                  Два вопроса:
                  1) James Covax действительно очень доходчиво объяснил, что такое IoC, вплоть до самостоятельной реализации простейшего IoC-контейнера. Он использовал так называемый static gateway, то есть создал статическую обертку для UnityContainer, чтобы в любом конструкторе иметь доступ ко всем зарегистрированным в контейнере типам. Насколько эта практика хороша?
                  2) Подскажите, как реализовать вот какую вещь. Пусть у нас есть сервис MyService и потребитель сервиса Consumer.
                  при этом
                  class Consumer{
                  public string DoSomething(string arg){
                  MyService service = new MyService(arg);
                  return service.GetSomething();
                  }
                  }
                  Подскажите, как приспособить этот код под IoC? У меня вызвал затруднение вот этот внутренний вызов конструктора с аргументами.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.