понедельник, 16 марта 2009 г.

Initializable pattern

Представьте, что вы разрабатываете API, в котором объекты, являющиеся частью системы, должны быть проинициализированы перед использованием.
Есть несколько способов простимулировать будущих пользователей этого API завершать инициализацию перед использованием:

  1. Ввести метод Initialize в каждом объекте, который нужно проинициаллизировать;
  2. Более "правильный" и следующий дизайну .Net framework - реализовать в классе ISupportInitialize;
  3. Пойти еще дальше и автоматизировать запуск и закрытие сессии инициализации;
Что значит автоматизировать запуск и закрытие сессии инициализации? Скажем, возложить эту обязанность на framerwork. Как это сделать? Об этом и заметка! Вспомните мой пост про удобную обертку над Monitor'ом, с помощью которой можно узнать есть ли в очереди на доступ к ресурсу заблокированные потоки? Так вот для пущего удобства и сохранения стиля выполнения блокировок с поддержкой языковых конструкций (скобочек =) ) там использовался .Net-специфичный паттерн Disposable. Почему бы не воспользоваться тем же приемом для обрамления сессии инициализации? Сказано, сделано. Прошу любить и жаловать - .Net-specific Initializable pattern. Состоит это решение из 2х частей:
  • собственно объект, который необходимо проинициаллизировать (реализует ISupportInitialize);
  • обертка, с помощью которой и происходит неявное управление сессией инициаллизации;
Давайте взглянем на эту обертку поближе:

Это маленькая обертка, которая при создании получает ссылку на инициаллизируемый объект и запускает сессию, вызовом метода ISupportInitialize.BeginInit(). И при детерминированном освобождении закрывает сессию вызовом метода ISupportInitialize.EndInit(). Я намеренно не стал реализовывать в этом классе Disposable паттерн целиком с тем, чтобы сессия закрывалась только явно, либо вызовом Dispose() обертки, либо EndInit() самого объекта.

Вот как может выглядеть API:



и использование его в коде:



Easy-peasy
!
А вот как выглядел бы код без Initializer:

мое чувство прекрасного плачет =).

Все это конечно не отменяет проверок на наличие сессии в самом объекте, т.к. он должен разрешать редактировать себя только на протяжение последней, а в остальном мне кажется несколько упрощает код, избавляя его от 2х строчек =), зато визуально ограничивает рамки сессии, что на мой взгляд весьма удобно и лаконично.

UPD: поступила вполне обоснованная критика - трудно читаемый код, тяжело разобраться зачем создавать аккаунт и сразу же "диспозить" его. На это могу лишь ответить, что API в данном случае (и вообще, если применяется этот паттерн) должно подсказывать ЧТО оно возвращает, другими словами метод называется не AddAccount, a InitializeAndAddAccount. А еще лучше если бы он назывался CreateAccountInitializer =).

UPD#2:
Придумал как избавиться от неразберихи и нарушения принципов, сохранив красоту идеи.

Не надо делать никаких "conventions" в API. Надо дать возможность желающим писать так как они хотят и привыкли, а извращенцам вроде меня дать возможность "юзать" такие "извраты":


и пример:



как видите API ничуть не пострадало и осталось читабельным и ясным, и ничьи принципы не нарушены.


PS: И еще одно дополнение. Вся эта возня вокруг Disposable паттерна - это ничто иное как smart pointer для .Net! Те из вас, кто писал на С++ поймут о чем я.

Удачи!