the mine universe

Показаны сообщения с ярлыком IoC. Показать все сообщения
Показаны сообщения с ярлыком IoC. Показать все сообщения

вторник, 4 августа 2009 г.

Будьте очень осторожны! Unity application block

Почти сразу после публикации своей заметки о сложностях с Unity, мне попался пост Ивана (Merle) об func – такая другая реализация IoC и DI концепции с поинтом на производительность и совместимость с Compact Framework. В нем – теже лямбды в регистрации. Помимо сложностей с добавлением новых реализаций «Lifetime Manager», позволю себе обратить внимание на менее очевидные трудности в Unity.

Не буду писать про недоразумение с RegisterInstance и PerThreadLifetimeManager. Очень кратко опишу концептуальные джоки (jokes), которые меня вводят в ступор после тсчательного знакомства с Unity:

  • Named Resolving - это самый верный способ запутать себя, коллег, и пользователей вашей библиотеки. Почему? Вспомним что такое Unity прежде всего - IoC и DI. Метод Resolve, во-первых, возвращает экземпляр определенного поведения. Этот экземпляр, прежде всего, исполняет некую роль в приложении, в системе, в библиотеке. Не побоюсь сделать на этом акцент - два вызова Resolve с одинаковыми аргументами должны возвращать две ссылки на экземпляры с одинаковым поведением. А что значит в таком свете Named Instances - это значит, что Resolve возвращает экземпляр как бы другого поведения!!! Т.е. Resolve<IService>("foo") и Resolve<IService>("bar") возвращают экземпляры IService разного поведения. Пока все хорошо? Ничуть! Если вы следите за моей мыслью, у вас должен возникнуть вопрос – «С какого перепуга параметризация поведения должна описываться единственной строкой?» Ни-с-ка-ко-го!!! Очень редко поведение можно параметризировать (ну и слово) строкой. Для пользователя вашей системы или библиотеки будет понятней, когда параметры будут явно описаны где-то в интерфейсе IService - будет видно и от каких именно параметров, и что именно зависит от параметров.

  • Injection на рефлексии. Ctrl-Shift-G в студии не покажет buildup-зависимости, спрятанные с помощью Unity. Unity придется подождать, пока Re# и VS научатся разглядывать Unity.

  • Circular references полностью на откупе гадалкам. Если вам повезло, и вы никогда-никогда не сталкивались с кошмаром StackOverflowException, боюсь мне будет трудно объяснить, почему же последний пункт не менее важен, чем все остальные выше.

пятница, 10 июля 2009 г.

Будьте осторожны: Unity Application Block

Столько шума и публикаций вокруг Unity не оставили меня равнодушным. Мне лично захотелось поближе познакомиться с Unity, когда я узнал что в Unity 1.2 появился Per Thread Storage Lifetime Manager. Подумать только! Для каждого потока возвращается отдельный экземпляр в методе Resolve. Не так давно коллега прислал ссылку на Unity без коментариев – дескать в продукте надобы использовать его вместо Dictionary<Type, object>, который выполняет роль контейнера зарегистрированных сервисов. Пришлось познакомиться с Unity поближе. И мое терпение лопнуло. Из моего поста вы узнаете, что Unity – это плохой, неудачный дизайн, его сложно использовать в реальных ситуациях, и сам Application Block “плохо пахнет”.

Для начала – почему Unity это over-design. Ответ - анонимный делегат. Да! Проще всего сконфигурировать контейнер и инвертировать зависимости с помощью анониного делегата в методе Register. Сразу отпадает необходимость в развесистой и неудобной иаерархии с красивым названием “Lifetime Managers”. А вместе с ней "в топку" отправим и другую избушку на курьих ножках (e.g. кривая, неудобная реализация) вокруг концепции "Injection of Contructor, Properties и Initializers".

Transient Lifetime Manager

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        return new SampleClass();
    });

Singleton (aka RegisterInstance)

SampleClass instance = new SampleClass();
ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        return instance;
    });

Injection of Contructors, Properties and Initialisers

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        SampleClass ret = new SampleClass(12, "Bye-bye, Unity!");
        ret.MyProperty = new object();
        ret.MyStringProperty = "Some Text";
        ret.InitializeMe(42.0m, container.Resolve<ILogger>());
        return ret;
    });

Для всех кто не читает справку приведу пример из справки Unity. Почувствуйте разницу:

IUnityContainer myContainer = new UnityContainer();
myContainer.Configure<InjectedMembers>()
  .ConfigureInjectionFor<MyObject>(
    new InjectionConstructor(12, "Hello Unity!"),
    new InjectionProperty("MyProperty"),
    new InjectionProperty("MyStringProperty", "SomeText"),
    new InjectionMethod("InitializeMe", 42.0, 
            new ResolvedParameter(typeof(ILogger), "SpecialLogger"))
  );

PerThreadLifetimeManager

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        const string key = "Sample Interface Implementation";
        LocalDataStoreSlot slot = Thread.GetNamedDataSlot(key);
        SampleClass ret = (SampleClass)Thread.GetData(slot);
        if (ret == null)
        {
            ret = new SampleClass();
            Thread.SetData(slot, ret);
        }
        return ret;
    });

TimeLimitedLifetimeManager

И в заключении – немного о том почему Unity сложен в использовании. Здесь я приведу очень древний пример реализации конецепции Lifetime Manager. Реализация древняя настолько, насколько древний сам .Net. Я пользуюсь реализацией ниже со времен появления .Net в 2002-м году. Для тех, кто не знает, напомню – Unity вышел в 2008 году. Сама концепция описывается одним простым предложением – Объекты “живут” в контейнере строго ограниченное время. Т.е. по истечении определенного времени объект из контейнера освобождается. Итак – сама реализация:

ConfigurationContainer container = new ConfigurationContainer();
container.Register<ISampleInterface>(
    delegate
    {
        const string key = "Sample Interface Implementation";
        SampleClass ret = (SampleClass)HttpRuntime.Cache.Get(key);
        if (ret == null)
        {
            ret = new SampleClass();
            HttpRuntime.Cache.Insert(
                key,
                ret,
                null,
                DateTime.Now.AddMinutes(60),
                Cache.NoSlidingExpiration);
        }
        return ret;
    });

Дополнение: Lazy Singleton Lifetime Manager

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

 ConfigurationContainer container = new ConfigurationContainer();
 container.Register<ISampleInterface>(
     delegate
     {
         return Lazy.Get(
             "Sample Interface Implementation",
             () => new SampleClass(container.Resolve<IFoo>(),"2");
             );
     });

Продолжение

Ярлыки (Tags)