Страницы

четверг, 26 июля 2012 г.

Головокружительные возможности Dependency Injection и Delphi Spring. Часть 7. Контроль над созданием.

Это перевод публикации Ника Ходжеса от 08 октября 2011 года: Getting Giddy with Dependency Injection and Delphi Spring #7 – Controlling Construction.


Все переводы по Spring


Как обычно, Delphi Spring Framework можно скачать с GoogleCode.

К настоящему времени вы получили представление о внедрении зависимостей ­и о том, как его использовать для разрывания связей в коде и создания объектов. Надеюсь, вы понимаете преимущества от написания кода ориентированного на интерфейсы, а не на реализацию. И если дела идут действительно хорошо, то вы уже с легкостью пишете модульные тесты, ведь ваш код теперь так легко тестировать.

Держу пари, что некоторые из вас думают, что тут не обошлось без магии и наверняка там, за кулисами, происходит слишком много всего.

Вы правы на этот счёт – там действительно много всего происходит. Delphi Spring Framework делает за вас кучу работы – в основном за счёт создания экземпляров классов используя RTTI (информацию о типах времени выполнения).­ Spring контролирует создание, но при этом, оставляет программисту возможность управлять жизненным циклом объектов.

Я также держу пари, что не все из вас готовы положиться на магию. Несмотря на то, что передача управления хорошо работает во многих случаях, вы можете захотеть не отдавать контроль над созданием объектов во всех случаях.

Хорошая новость здесь в том, что вам и не придётся. Фреймворк, при желании, позволяет управлять созданием объектов. В большинстве случаев если классы хорошо спроектированы, то их конструкторы просты, и в них не происходит ничего, кроме присвоения значений. Но иногда возникает нужда сделать при создании объекта что-то особенное. Иногда, классу требуется динамический параметр – объект, чьё состояние будет известно только при выполнении. (Примечание переводчика: мне очень не нравится слово динамический в этом контексте и примеры Ника, но я не придумал лучшего объяснения. Есть идеи, читатель? Пиши в комментарии!) Например, у нас может быть класс Клиент, и у этого Клиента могут быть Счета. И у нас есть класс, который мы хотим передать этому клиенту, но при этом мы не хотим связывать эти классы. Наш Клиент динамический – мы можем запускать запрос и выполнять эту операцию на каждом клиенте в нашей базе данных. Или Клиент может что-то делать на нашем сайте, и таким образом наш объект Клиента будет относиться именно к этому конкретному клиенту. В любом случае, данные нашего Клиента динамичны во время выполнения, и поэтому мы не можем загнать создание объекта в жёсткие рамки. Он должен быть уникальным, каждый раз, когда нам нужен экземпляр объекта, который выполняет действие над данным клиентом.

Нижеприведённый код взят из каталога примеров (Samples) фреймворка Delphi Spring.  Это часть проекта Demo.Spring.DelegatedConstructor.

Фреймворк Spring позволяет контролировать создание объектов при помощи класса TRegistration, который используется для регистрации типов с интерфейсами. При регистрации класса, мы можем передать в качестве параметра анонимный метод типа TActivatorDelegate, объявленный следующим образом:

TActivatorDelegate<T: class> = reference to function: T;

Класс TRegistration реализует текучий интерфейс (fluent interface), что даёт возможность выполнить любое количество действий при регистрации. Например, если у нас есть проект, включающий в себя динамический класс TUser, и класс TUserProcessor, который требует в качестве параметра экземпляр TUser, то регистрация может выглядеть следующим образом:

GlobalContainer.RegisterType<TUserProcessor>.Implements<IUserUpgrader>.AsTransient.DelegateTo(
  function: TUserProcessor
  begin
    Result := TUserProcessor.Create(GetCurrentUser);
  end
);

Класс TUserProcessor зарегистрирован как реализующий интерфейс IUserUpgrader. Жизненный цикл результирующего класса указан как “Transient” (примечание переводчика: с англ. временный, скоротечный). Это означает, что объект будет жить «обычной жизнью». Т.е. он будет уничтожен, когда интерфейсная ссылка выйдет за пределы области видимости. (Обратите внимание, что жизненным циклом по умолчанию является AsTransient. Управление жизненным циклом мы обсудим в следующих публикациях.)

Реальное создание экземпляра класса TUserProcessor делегировано анонимному методу – в данном случае функции, которая просто возвращает экземпляр класса TUserProcessor. Ключевой момент здесь в том, что этот анонимный метод вызывает функцию GetCurrentUser, которая внедрит экземпляр текущего динамического TUser в результирующий объект. В этом анонимном методе вы можете сделать всё что угодно, включая создание и настройку результирующего объекта в предпочтительном виде. Вы можете устанавливать свойства и даже вызывать методы результирующего объекта.

Если посмотреть код проекта, то можно заметить, что он просто выводит текст в консоль. GetCurrentUser – это просто одиночка (singleton), возвращающий тот же самый экземпляр TUser. Этот код носит иллюстративный характер, поэтому внутри он не делает ничего серьезного. В реальном же приложении вызов GetCurrentUser будет делать именно то, о чём мы писали — возвращать экземпляр TUser, отражающий текущее динамическое состояние обсуждаемого пользователя. Важно отметить, что мы можем контролировать создание TUserProcessor настолько детально, насколько мы пожелаем.

И наконец, когда мы обратимся к ServiceLocator-у, чтобы получить экземпляр объекта, контейнер вызовет для нас этот анонимный метод и возвратит экземпляр, созданный именно так, как нам это нужно.

Таким образом Spring Container позволяет целиком и полностью контролировать создание объектов. А это даёт нам возможность приправить творящуюся внутри контейнера него «магию» своим собственным волшебным порошком.

Дополнение из комментариев к статье:

Более того, вы можете использовать метод(ы) GlobalContainer.Resolve чтобы во время выполнения запросить экземпляр сервиса внутри анонимного метода:

GlobalContainer.RegisterType<TUserProcessor>.Implements<IUserUpgrader>.AsTransient.DelegateTo(
  function: TUserProcessor 
  begin 
    Result := TUserProcessor.Create(GetCurrentUser); 
    Result.Logger := GlobalContainer.Resolve<ILogger>;  // <- Dynamic 
  end 
);

Ссылки по теме

6 комментариев:

  1. Может быть, я не обратил внимания, но в DelphiFeeds я не видел ни одной из предыдущих частей из цикла статей про Delphi Spring. Они были, или их забыли добавить )) ?

    ОтветитьУдалить
    Ответы
    1. Все, увидел, простите. Они просто шли с большим интервалом((.

      Удалить
    2. Тут уже моя вина. Долго перевожу, редко публикую. На блог времени очень мало остаётся. )

      Удалить
    3. Добавьте, пожалуйста, оглавления серии во все статьи серии, а не только в последнюю.

      Удалить
  2. Похоже при использовании DelegateTo происходит утечка памяти (

    ОтветитьУдалить