30 июня 2009 в 21:43

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

.NET*
В продолжение темы по 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/>
Вот пока и все. Продолжение следует!
Дмитpий Hecтepук @mezastel
карма
112,8
рейтинг 0,1
Пользователь
Похожие публикации
Самое читаемое Разработка

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

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