the mine universe

пятница, 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)