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
              Добавил
                0
                спасибо.
              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? У меня вызвал затруднение вот этот внутренний вызов конструктора с аргументами.

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

                    Самое читаемое