Это перевод публикации Ника Ходжеса от 5 ноября 2011 года: Getting Giddy with Dependency Injection and Delphi Spring #8 – Miscellanea. (перевод сделан с разрешения автора).
Все переводы по Spring
Как обычно, Delphi Spring Framework можно скачать с GoogleCode.
На данный момент мы познакомились со значительной частью основ внедрения зависимости. И это далеко не всё, нам ещё много чего предстоит узнать, но именно в этой статье я хочу остановиться и обсудить пару вещей, о которые стоит поговорить подробнее. Так что лишних церемоний, вот они в виде списка:
- Как вы наверно заметили, я призываю вас использовать интерфейсы при кодировании и регистрировать классы, реализующие эти интерфейсы в фреймворке. О чём я забыл упомянуть явно, это о том, что все интерфейсы, зарегистрированные в фреймворке Spring должны иметь GUID. GUID легко добавить, нажав CTRL+SHIFT+G в редакторе кода Delphi. А если же вы попробуете использовать интерфейс, у которого нет GUID-а, то получите ошибку: “Project <НазваниеПроекта>.exe raised exception class ERegistrationException with message 'Non-Guid Interface Services are not supported.” («Интерфейсы без Guid не поддерживаются»).
- Если внимательно посмотреть на код в демонстрационных проектах, то можно заметить, что прежде чем что-либо делать с контейнером Spring, необходимо вызвать GlobalContainer.Build. Этот метод должен быть вызван до того, как что-либо может быть получено из ServiceLocator. Метод Build это код, который собирает воедино все зарегистрированные классы, создаёт экземпляры классов или подготавливает их к созданию по запросу, с учётом указанного времени жизни. Если при запуске приложения вы получаете ошибку 'LifetimeTypeManager was expected.', то это с большой вероятностью означает, что вам необходимо вызвать Build для вашего контейнера. И в самом деле, метод Build является основой вашего контейнера Внедрения Зависимостей. Вы должны один раз вызвать метод Build в корне вашего приложения. Для Delphi разработчиков это означает, что он должен быть вызван одной из первых строчек в вашем DPR-файле. Это также означает, что классы лучше регистрировать как можно раньше.
- Вы не обязаны использовать Global Container и Service Locator предоставляемые вам фреймворком. Создание своих собственных вариантов более чем приветствуется. У нас в Gateway Ticketing мы сделали следующее: мы объявили интерфейс, являющийся полной абстракцией понятия Контейнер DI (DI Container = Контейнер Обращения Зависимостей), а затем реализовали этот интерфейс самостоятельно, используя наследника от Tcontainer из фреймворка Delphi Spring. Таким образом, если в фреймворке что-то изменяется (а он действительно меняется с тех самых пор, как мы начали его использовать) или, если мы решим использовать другой контейнер, наш код полностью отделён от какой-либо конкретной реализации. Ещё один хороший пример следования правилу «Пиши код для абстракций, а не реализации». :)
- Я вынужден отметить, что некоторые считают концепцию Service Locator анти-паттерном . Сам я пока что ещё не пришёл к твёрдому заключению по этому вопросу. Да, Service Locator почти всегда является Одиночкой, но я лично и не рассматриваю одиночек только для чтения как что-то плохое, в отличие от некоторых. (Одиночка для чтения и для записи, по сути, является глобальной переменной, и я думаю, что все согласятся с тем, что глобальные переменные являются Порождением Сатаны.) Однако существуют некоторые разногласия в том, является ли использование контейнера реализацией Service Locator. Кроме того, если все зависимости определены до того, как результат выполнения станет доступным пользователю, то можно ли вообще считать контейнер переменной? Это остается предметом дискуссий. В конечном счете, для меня ценность и польза от Service Locator с лихвой перевешивает все возможные недостатки.
- Существует один недостаток, возвращающий нас к предыдущему пункту, который заключается в том, что когда вещи настолько отделены друг от друга и позднее связывание настолько ярко выражено, то становится возможным успешно собрать программу и до самого её запуска так и не узнать о том, что мы забыли зарегистрировать необходимый реализующий класс. В сложной системе, может пройти немало времени, прежде чем кто-либо заметит, что отсутствует реализующий класс для какого-нибудь редко используемого интерфейса. Если вы следуете предложенному мной образцу регистрации реализации в initialization секции модуля, то вы обязаны использовать этот модуль где-нибудь в программе (примечание переводчика: включить в uses), чтобы регистрация сработала. Как уже говорилось, если вы забыли его добавить, и не включили этот модуль в свою программу, то компилятор вам ничего не скажет, и узнаете вы об этом только во время работы программы. Это слабое место, и вам следует быть очень внимательным, чтобы не попасться в эту ловушку. В качестве стратегии можно рассматривать выделение всех регистраций в единый модуль, или использовать что-то типа статического анализа кода, который проверит, что для каждого вызова GetService есть соответствующий вызов RegisterService (или как там эти методы у вас называются). Просто, чтобы вы были в курсе.
Это всего лишь несколько вещей, на которые стоит обратить внимание, при использовании Dependency Injection в вашем коде. Я говорил это раньше, я скажу это снова: Если вы не используете Dependency Injection, значит, вы работаете неправильно.
Ссылки по теме